diff --git a/android/app/capacitor.build.gradle b/android/app/capacitor.build.gradle index 9f6e577..739289a 100644 --- a/android/app/capacitor.build.gradle +++ b/android/app/capacitor.build.gradle @@ -13,13 +13,16 @@ dependencies { implementation project(':capacitor-app') implementation project(':capacitor-browser') implementation project(':capacitor-device') + implementation project(':capacitor-local-llm') implementation project(':capacitor-local-notifications') implementation project(':capacitor-network') implementation project(':capacitor-preferences') implementation project(':capacitor-push-notifications') implementation project(':capacitor-status-bar') + implementation project(':capgo-capacitor-document-scanner') implementation project(':capgo-capacitor-nfc') implementation project(':capgo-capacitor-social-login') + implementation project(':jcesarmobile-capacitor-ocr') implementation project(':revenuecat-purchases-capacitor') implementation project(':revenuecat-purchases-capacitor-ui') implementation project(':capacitor-plugin-safe-area') diff --git a/android/build.gradle b/android/build.gradle index f1b3b0e..97e6322 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -7,7 +7,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.7.2' + classpath 'com.android.tools.build:gradle:8.10.1' classpath 'com.google.gms:google-services:4.4.2' // NOTE: Do not place your application dependencies here; they belong diff --git a/android/capacitor.settings.gradle b/android/capacitor.settings.gradle index e49f972..a50983d 100644 --- a/android/capacitor.settings.gradle +++ b/android/capacitor.settings.gradle @@ -14,6 +14,9 @@ project(':capacitor-browser').projectDir = new File('../node_modules/@capacitor/ include ':capacitor-device' project(':capacitor-device').projectDir = new File('../node_modules/@capacitor/device/android') +include ':capacitor-local-llm' +project(':capacitor-local-llm').projectDir = new File('../node_modules/@capacitor/local-llm/android') + include ':capacitor-local-notifications' project(':capacitor-local-notifications').projectDir = new File('../node_modules/@capacitor/local-notifications/android') @@ -29,12 +32,18 @@ project(':capacitor-push-notifications').projectDir = new File('../node_modules/ include ':capacitor-status-bar' project(':capacitor-status-bar').projectDir = new File('../node_modules/@capacitor/status-bar/android') +include ':capgo-capacitor-document-scanner' +project(':capgo-capacitor-document-scanner').projectDir = new File('../node_modules/@capgo/capacitor-document-scanner/android') + include ':capgo-capacitor-nfc' project(':capgo-capacitor-nfc').projectDir = new File('../node_modules/@capgo/capacitor-nfc/android') include ':capgo-capacitor-social-login' project(':capgo-capacitor-social-login').projectDir = new File('../node_modules/@capgo/capacitor-social-login/android') +include ':jcesarmobile-capacitor-ocr' +project(':jcesarmobile-capacitor-ocr').projectDir = new File('../node_modules/@jcesarmobile/capacitor-ocr/android') + include ':revenuecat-purchases-capacitor' project(':revenuecat-purchases-capacitor').projectDir = new File('../node_modules/@revenuecat/purchases-capacitor/android') diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index cea7a79..37f853b 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/android/variables.gradle b/android/variables.gradle index d7809e1..753826f 100644 --- a/android/variables.gradle +++ b/android/variables.gradle @@ -1,17 +1,17 @@ ext { - minSdkVersion = 24 - compileSdkVersion = 35 - targetSdkVersion = 35 - androidxActivityVersion = '1.9.2' - androidxAppCompatVersion = '1.7.0' - androidxCoordinatorLayoutVersion = '1.2.0' - androidxCoreVersion = '1.15.0' - androidxFragmentVersion = '1.8.4' + minSdkVersion = 28 + compileSdkVersion = 36 + targetSdkVersion = 36 + androidxActivityVersion = '1.11.0' + androidxAppCompatVersion = '1.7.1' + androidxCoordinatorLayoutVersion = '1.3.0' + androidxCoreVersion = '1.17.0' + androidxFragmentVersion = '1.8.9' coreSplashScreenVersion = '1.0.1' - androidxWebkitVersion = '1.12.1' + androidxWebkitVersion = '1.14.0' junitVersion = '4.13.2' - androidxJunitVersion = '1.2.1' - androidxEspressoCoreVersion = '3.6.1' + androidxJunitVersion = '1.3.0' + androidxEspressoCoreVersion = '3.7.0' cordovaAndroidVersion = '10.1.1' firebaseMessagingVersion = '24.1.0' -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index c02d640..bc249e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "donetick", - "version": "1.2.2", + "version": "1.2.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "donetick", - "version": "1.2.2", + "version": "1.2.3", "dependencies": { "@capacitor-community/sqlite": "^7.0.0", "@capacitor/android": "^7.0.0", @@ -15,11 +15,13 @@ "@capacitor/core": "^7.0.0", "@capacitor/device": "^7.0.0", "@capacitor/ios": "^7.0.0", + "@capacitor/local-llm": "^1.0.0", "@capacitor/local-notifications": "^7.0.0", "@capacitor/network": "^7.0.1", "@capacitor/preferences": "^7.0.0", "@capacitor/push-notifications": "^7.0.0", "@capacitor/status-bar": "^7.0.0", + "@capgo/capacitor-document-scanner": "^8.4.0", "@capgo/capacitor-nfc": "^7.2.0", "@capgo/capacitor-social-login": "^7.5.3", "@dnd-kit/core": "^6.3.1", @@ -28,6 +30,7 @@ "@emotion/react": "^11.11.3", "@emotion/styled": "^11.11.0", "@hello-pangea/dnd": "^18.0.1", + "@jcesarmobile/capacitor-ocr": "^0.3.0", "@meauxt/react-swipeable-list": "^1.0.0", "@mui/icons-material": "^5.16.13", "@mui/joy": "^5.0.0-beta.20", @@ -66,6 +69,7 @@ "reactjs-social-login": "^2.6.3", "recharts": "^2.15.0", "reusify": "^1.0.4", + "tesseract.js": "^7.0.0", "vite-plugin-pwa": "^0.20.0" }, "devDependencies": { @@ -125,7 +129,7 @@ }, "node_modules/@antfu/utils": { "version": "0.7.10", - "devOptional": true, + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" @@ -152,8 +156,6 @@ }, "node_modules/@babel/core": { "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", - "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", @@ -465,8 +467,6 @@ }, "node_modules/@babel/helpers": { "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz", - "integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==", "license": "MIT", "dependencies": { "@babel/template": "^7.27.2", @@ -1414,8 +1414,6 @@ }, "node_modules/@babel/runtime": { "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", - "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1451,8 +1449,6 @@ }, "node_modules/@babel/types": { "version": "7.28.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", - "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -1464,7 +1460,7 @@ }, "node_modules/@canvas/image-data": { "version": "1.0.0", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/@capacitor-community/sqlite": { @@ -1482,8 +1478,6 @@ }, "node_modules/@capacitor-community/sqlite/node_modules/@rollup/rollup-darwin-arm64": { "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.9.tgz", - "integrity": "sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ==", "cpu": [ "arm64" ], @@ -1493,10 +1487,111 @@ "darwin" ] }, + "node_modules/@capacitor-community/sqlite/node_modules/@rollup/rollup-darwin-x64": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.9.tgz", + "integrity": "sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@capacitor-community/sqlite/node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.9.tgz", + "integrity": "sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@capacitor-community/sqlite/node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.9.tgz", + "integrity": "sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@capacitor-community/sqlite/node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.9.tgz", + "integrity": "sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@capacitor-community/sqlite/node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.9.tgz", + "integrity": "sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@capacitor-community/sqlite/node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.9.tgz", + "integrity": "sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@capacitor-community/sqlite/node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.9.tgz", + "integrity": "sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@capacitor-community/sqlite/node_modules/@stencil/core": { "version": "4.36.3", - "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.36.3.tgz", - "integrity": "sha512-C9DOaAjm+hSYRuVoUuYWG/lrYT8+4DG0AL0m1Ea9+G5v2Y6ApVpNJLbXvFlRZIdDMGecH86s6v0Gp39uockLxg==", "license": "MIT", "bin": { "stencil": "bin/stencil" @@ -1518,8 +1613,6 @@ }, "node_modules/@capacitor-community/sqlite/node_modules/jeep-sqlite": { "version": "2.8.0", - "resolved": "https://registry.npmjs.org/jeep-sqlite/-/jeep-sqlite-2.8.0.tgz", - "integrity": "sha512-FWNUP6OAmrUHwiW7H1xH5YUQ8tN2O4l4psT1sLd7DQtHd5PfrA1nvNdeKPNj+wQBtu7elJa8WoUibTytNTaaCg==", "license": "MIT", "dependencies": { "@stencil/core": "^4.20.0", @@ -1531,8 +1624,6 @@ }, "node_modules/@capacitor-community/sqlite/node_modules/sql.js": { "version": "1.13.0", - "resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.13.0.tgz", - "integrity": "sha512-RJbVP1HRDlUUXahJ7VMTcu9Rm1Nzw+EBpoPr94vnbD4LwR715F3CcxE2G2k45PewcaZ57pjetYa+LoSJLAASgA==", "license": "MIT" }, "node_modules/@capacitor/android": { @@ -1869,8 +1960,6 @@ }, "node_modules/@capacitor/cli/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": { @@ -1883,8 +1972,6 @@ }, "node_modules/@capacitor/cli/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": { @@ -1912,6 +1999,13 @@ "@capacitor/core": "^7.4.0" } }, + "node_modules/@capacitor/local-llm": { + "version": "1.0.0", + "license": "MIT", + "peerDependencies": { + "@capacitor/core": ">=8.0.0" + } + }, "node_modules/@capacitor/local-notifications": { "version": "7.0.2", "license": "MIT", @@ -1947,6 +2041,13 @@ "@capacitor/core": ">=7.0.0" } }, + "node_modules/@capgo/capacitor-document-scanner": { + "version": "8.4.0", + "license": "MPL-2.0", + "peerDependencies": { + "@capacitor/core": ">=8.0.0" + } + }, "node_modules/@capgo/capacitor-nfc": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@capgo/capacitor-nfc/-/capacitor-nfc-7.2.0.tgz", @@ -2148,6 +2249,380 @@ "version": "0.4.0", "license": "MIT" }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.8.0", "dev": true, @@ -2564,6 +3039,13 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/@jcesarmobile/capacitor-ocr": { + "version": "0.3.0", + "license": "MIT", + "peerDependencies": { + "@capacitor/core": ">=8.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "license": "MIT", @@ -2601,8 +3083,6 @@ }, "node_modules/@meauxt/react-swipeable-list": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@meauxt/react-swipeable-list/-/react-swipeable-list-1.0.0.tgz", - "integrity": "sha512-LhcGbX9otSfm788p0I43Q4keKNsvl/hbFhVszEpI8f4+EyyU9+Gc6Sn6HajVUCX4Enh4Wk1G1DDrmA2+HnpB/A==", "license": "MIT" }, "node_modules/@medv/finder": { @@ -2611,9 +3091,6 @@ }, "node_modules/@mui/base": { "version": "5.0.0-beta.40-1", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40-1.tgz", - "integrity": "sha512-agKXuNNy0bHUmeU7pNmoZwNFr7Hiyhojkb9+2PVyDG5+6RafYuyMgbrav8CndsB7KUc/U51JAw9vKNDLYBzaUA==", - "deprecated": "This package has been replaced by @base-ui-components/react", "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.9", @@ -2652,8 +3129,6 @@ }, "node_modules/@mui/icons-material": { "version": "5.18.0", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.18.0.tgz", - "integrity": "sha512-1s0vEZj5XFXDMmz3Arl/R7IncFqJ+WQ95LDp1roHWGDE2oCO3IS4/hmiOv1/8SD9r6B7tv9GLiqVZYHo+6PkTg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.9" @@ -2678,8 +3153,6 @@ }, "node_modules/@mui/joy": { "version": "5.0.0-beta.52", - "resolved": "https://registry.npmjs.org/@mui/joy/-/joy-5.0.0-beta.52.tgz", - "integrity": "sha512-e8jQanA5M1f/X52mJrw0UIW8Er7EAHuLuigmGFw7yIsAgIluhIP4rZ7JcbVrUi6z5Gk0weC9QWUUtjLejAbO8g==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.9", @@ -2719,8 +3192,6 @@ }, "node_modules/@mui/material": { "version": "5.18.0", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.18.0.tgz", - "integrity": "sha512-bbH/HaJZpFtXGvWg3TsBWG4eyt3gah3E7nCNU8GLyRjVoWcA91Vm/T+sjHfUcwgJSw9iLtucfHBoq+qW/T30aA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.9", @@ -2820,8 +3291,6 @@ }, "node_modules/@mui/system": { "version": "5.18.0", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.18.0.tgz", - "integrity": "sha512-ojZGVcRWqWhu557cdO3pWHloIGJdzVtxs3rk0F9L+x55LsUjcMUVkEhiF7E4TMxZoF9MmIHGGs0ZX3FDLAf0Xw==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.9", @@ -2872,8 +3341,6 @@ }, "node_modules/@mui/utils": { "version": "5.17.1", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.17.1.tgz", - "integrity": "sha512-jEZ8FTqInt2WzxDV8bhImWBqeQRD99c/id/fq83H0ER9tFl+sfZlaAoCdznGvbSQQ9ividMxqSV2c7cC1vBcQg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.9", @@ -3059,9 +3526,9 @@ } }, "node_modules/@oclif/core/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.0.tgz", + "integrity": "sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==", "dev": true, "license": "MIT", "dependencies": { @@ -3194,9 +3661,9 @@ } }, "node_modules/@oclif/plugin-autocomplete/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.0.tgz", + "integrity": "sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==", "dev": true, "license": "MIT", "dependencies": { @@ -3304,9 +3771,9 @@ } }, "node_modules/@oclif/plugin-commands/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.0.tgz", + "integrity": "sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==", "dev": true, "license": "MIT", "dependencies": { @@ -3413,9 +3880,9 @@ } }, "node_modules/@oclif/plugin-help/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.0.tgz", + "integrity": "sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==", "dev": true, "license": "MIT", "dependencies": { @@ -3548,9 +4015,9 @@ } }, "node_modules/@oclif/plugin-plugins/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.0.tgz", + "integrity": "sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==", "dev": true, "license": "MIT", "dependencies": { @@ -3708,9 +4175,9 @@ } }, "node_modules/@oclif/plugin-version/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.0.tgz", + "integrity": "sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==", "dev": true, "license": "MIT", "dependencies": { @@ -3926,6 +4393,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3939,6 +4407,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3947,11 +4416,10 @@ }, "node_modules/@rollup/rollup-darwin-arm64": { "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.50.0.tgz", - "integrity": "sha512-vwSXQN8T4sKf1RHr1F0s98Pf8UPz7pS6P3LG9NSmuw0TVh7EmaE+5Ny7hJOZ0M2yuTctEsHHRTMi2wuHkdS6Hg==", "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3959,12 +4427,13 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.9.tgz", - "integrity": "sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q==", + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.50.0.tgz", + "integrity": "sha512-cQp/WG8HE7BCGyFVuzUg0FNmupxC+EPZEwWu2FCGGw5WDT1o2/YlENbm5e9SMvfDFR6FRhVCBePLqj0o8MN7Vw==", "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3978,6 +4447,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3991,6 +4461,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4004,6 +4475,10 @@ "cpu": [ "arm" ], + "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -4017,6 +4492,10 @@ "cpu": [ "arm" ], + "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -4024,12 +4503,16 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.9.tgz", - "integrity": "sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw==", + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.50.0.tgz", + "integrity": "sha512-1EhkSvUQXJsIhk4msxP5nNAUWoB4MFDHhtc4gAYvnqoHlaL9V3F37pNHabndawsfy/Tp7BPiy/aSa6XBYbaD1g==", "cpu": [ "arm64" ], + "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -4037,12 +4520,16 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.9.tgz", - "integrity": "sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A==", + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.50.0.tgz", + "integrity": "sha512-EtBDIZuDtVg75xIPIK1l5vCXNNCIRM0OBPUG+tbApDuJAy9mKago6QxX+tfMzbCI6tXEhMuZuN1+CU8iDW+0UQ==", "cpu": [ "arm64" ], + "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -4056,6 +4543,10 @@ "cpu": [ "loong64" ], + "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -4069,6 +4560,10 @@ "cpu": [ "ppc64" ], + "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -4082,6 +4577,10 @@ "cpu": [ "riscv64" ], + "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -4095,6 +4594,10 @@ "cpu": [ "riscv64" ], + "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -4108,6 +4611,10 @@ "cpu": [ "s390x" ], + "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -4115,12 +4622,16 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.9.tgz", - "integrity": "sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==", + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.50.0.tgz", + "integrity": "sha512-8PrJJA7/VU8ToHVEPu14FzuSAqVKyo5gg/J8xUerMbyNkWkO9j2ExBho/68RnJsMGNJq4zH114iAttgm7BZVkA==", "cpu": [ "x64" ], + "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -4128,12 +4639,16 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.9.tgz", - "integrity": "sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==", + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.50.0.tgz", + "integrity": "sha512-SkE6YQp+CzpyOrbw7Oc4MgXFvTw2UIBElvAvLCo230pyxOLmYwRPwZ/L5lBe/VW/qT1ZgND9wJfOsdy0XptRvw==", "cpu": [ "x64" ], + "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -4147,6 +4662,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4154,12 +4670,13 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.9.tgz", - "integrity": "sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q==", + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.50.0.tgz", + "integrity": "sha512-q7cIIdFvWQoaCbLDUyUc8YfR3Jh2xx3unO8Dn6/TTogKjfwrax9SyfmGGK6cQhKtjePI7jRfd7iRYcxYs93esg==", "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4173,6 +4690,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4180,12 +4698,13 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.34.9", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.9.tgz", - "integrity": "sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw==", + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.0.tgz", + "integrity": "sha512-xMmiWRR8sp72Zqwjgtf3QbZfF1wdh8X2ABu3EaozvZcyHJeU0r+XAnXdKgs4cCAp6ORoYoCygipYP1mjmbjrsg==", "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4230,8 +4749,6 @@ }, "node_modules/@swc/core-darwin-arm64": { "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.13.5.tgz", - "integrity": "sha512-lKNv7SujeXvKn16gvQqUQI5DdyY8v7xcoO3k06/FJbHJS90zEwZdQiMNRiqpYw/orU543tPaWgz7cIYWhbopiQ==", "cpu": [ "arm64" ], @@ -4244,23 +4761,7 @@ "node": ">=10" } }, - "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.13.5.tgz", - "integrity": "sha512-WD530qvHrki8Ywt/PloKUjaRKgstQqNGvmZl54g06kA+hqtSE2FTG9gngXr3UJxYu/cNAjJYiBifm7+w4nbHbA==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core/node_modules/@swc/core-darwin-x64": { + "node_modules/@swc/core-darwin-x64": { "version": "1.13.5", "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.13.5.tgz", "integrity": "sha512-ILd38Fg/w23vHb0yVjlWvQBoE37ZJTdlLHa8LRCFDdX4WKfnVBiblsCU9ar4QTMNdeTBEX9iUF4IrbNWhaF1Ng==", @@ -4276,6 +4777,146 @@ "node": ">=10" } }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.13.5.tgz", + "integrity": "sha512-Q6eS3Pt8GLkXxqz9TAw+AUk9HpVJt8Uzm54MvPsqp2yuGmY0/sNaPPNVqctCX9fu/Nu8eaWUen0si6iEiCsazQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.13.5.tgz", + "integrity": "sha512-aNDfeN+9af+y+M2MYfxCzCy/VDq7Z5YIbMqRI739o8Ganz6ST+27kjQFd8Y/57JN/hcnUEa9xqdS3XY7WaVtSw==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.13.5.tgz", + "integrity": "sha512-9+ZxFN5GJag4CnYnq6apKTnnezpfJhCumyz0504/JbHLo+Ue+ZtJnf3RhyA9W9TINtLE0bC4hKpWi8ZKoETyOQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.13.5.tgz", + "integrity": "sha512-WD530qvHrki8Ywt/PloKUjaRKgstQqNGvmZl54g06kA+hqtSE2FTG9gngXr3UJxYu/cNAjJYiBifm7+w4nbHbA==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.13.5.tgz", + "integrity": "sha512-Luj8y4OFYx4DHNQTWjdIuKTq2f5k6uSXICqx+FSabnXptaOBAbJHNbHT/06JZh6NRUouaf0mYXN0mcsqvkhd7Q==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.13.5.tgz", + "integrity": "sha512-cZ6UpumhF9SDJvv4DA2fo9WIzlNFuKSkZpZmPG1c+4PFSEMy5DFOjBSllCvnqihCabzXzpn6ykCwBmHpy31vQw==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.13.5.tgz", + "integrity": "sha512-C5Yi/xIikrFUzZcyGj9L3RpKljFvKiDMtyDzPKzlsDrKIw2EYY+bF88gB6oGY5RGmv4DAX8dbnpRAqgFD0FMEw==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.13.5.tgz", + "integrity": "sha512-YrKdMVxbYmlfybCSbRtrilc6UA8GF5aPmGKBdPvjrarvsmf4i7ZHGCEnLtfOMd3Lwbs2WUZq3WdMbozYeLU93Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, "node_modules/@swc/counter": { "version": "0.1.3", "license": "Apache-2.0" @@ -4304,8 +4945,6 @@ }, "node_modules/@tanstack/query-core": { "version": "5.85.9", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.85.9.tgz", - "integrity": "sha512-5fxb9vwyftYE6KFLhhhDyLr8NO75+Wpu7pmTo+TkwKmMX2oxZDoLwcqGP8ItKSpUMwk3urWgQDZfyWr5Jm9LsQ==", "license": "MIT", "funding": { "type": "github", @@ -4314,8 +4953,6 @@ }, "node_modules/@tanstack/react-query": { "version": "5.85.9", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.85.9.tgz", - "integrity": "sha512-2T5zgSpcOZXGkH/UObIbIkGmUPQqZqn7esVQFXLOze622h4spgWf5jmvrqAo9dnI13/hyMcNsF1jsoDcb59nJQ==", "license": "MIT", "dependencies": { "@tanstack/query-core": "5.85.9" @@ -4427,8 +5064,6 @@ }, "node_modules/@trapezedev/project/node_modules/@trapezedev/gradle-parse": { "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@trapezedev/gradle-parse/-/gradle-parse-7.1.3.tgz", - "integrity": "sha512-WQVF5pEJ5o/mUyvfGTG9nBKx9Te/ilKM3r2IT69GlbaooItT5ao7RyF1MUTBNjHLPk/xpGUY3c6PyVnjDlz0Vw==", "dev": true, "license": "SEE LICENSE" }, @@ -4445,8 +5080,6 @@ }, "node_modules/@trapezedev/project/node_modules/prettier": { "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true, "license": "MIT", "bin": { @@ -4553,7 +5186,7 @@ }, "node_modules/@types/node": { "version": "24.3.0", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "undici-types": "~7.10.0" @@ -4574,6 +5207,7 @@ }, "node_modules/@types/react": { "version": "18.3.24", + "dev": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -4710,8 +5344,6 @@ }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { "version": "8.42.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.42.0.tgz", - "integrity": "sha512-51+x9o78NBAVgQzOPd17DkNTnIzJ8T/O2dmMBLoK9qbY0Gm52XJcdJcCl18ExBMiHo6jPMErUQWUv5RLE51zJw==", "dev": true, "license": "MIT", "dependencies": { @@ -4760,7 +5392,7 @@ }, "node_modules/@vite-pwa/assets-generator": { "version": "0.2.6", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "cac": "^6.7.14", @@ -4809,8 +5441,6 @@ }, "node_modules/@xml-tools/parser/node_modules/chevrotain": { "version": "7.1.1", - "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-7.1.1.tgz", - "integrity": "sha512-wy3mC1x4ye+O+QkEinVJkPf5u2vsrDIYW9G7ZuwFl6v/Yu0LwUuT2POsb+NUWApebyxfkQq6+yDfRExbnI5rcw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -4980,8 +5610,6 @@ }, "node_modules/array-buffer-byte-length": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", - "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -5022,8 +5650,6 @@ }, "node_modules/array-includes/node_modules/es-abstract": { "version": "1.24.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", - "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", "dev": true, "license": "MIT", "dependencies": { @@ -5118,8 +5744,6 @@ }, "node_modules/array.prototype.findlast/node_modules/es-abstract": { "version": "1.24.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", - "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", "dev": true, "license": "MIT", "dependencies": { @@ -5204,8 +5828,6 @@ }, "node_modules/array.prototype.flat/node_modules/es-abstract": { "version": "1.24.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", - "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", "dev": true, "license": "MIT", "dependencies": { @@ -5290,8 +5912,6 @@ }, "node_modules/array.prototype.flatmap/node_modules/es-abstract": { "version": "1.24.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", - "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", "dev": true, "license": "MIT", "dependencies": { @@ -5374,8 +5994,6 @@ }, "node_modules/array.prototype.tosorted/node_modules/es-abstract": { "version": "1.24.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", - "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", "dev": true, "license": "MIT", "dependencies": { @@ -5443,8 +6061,6 @@ }, "node_modules/arraybuffer.prototype.slice": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", - "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.1", @@ -5464,8 +6080,6 @@ }, "node_modules/arraybuffer.prototype.slice/node_modules/es-abstract": { "version": "1.24.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", - "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.2", @@ -5557,8 +6171,6 @@ }, "node_modules/async-function": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", - "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -5609,8 +6221,6 @@ }, "node_modules/available-typed-arrays": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "license": "MIT", "dependencies": { "possible-typed-array-names": "^1.0.0" @@ -5624,7 +6234,7 @@ }, "node_modules/b4a": { "version": "1.6.7", - "devOptional": true, + "dev": true, "license": "Apache-2.0" }, "node_modules/babel-plugin-macros": { @@ -5686,7 +6296,7 @@ }, "node_modules/base64-js": { "version": "1.5.1", - "devOptional": true, + "dev": true, "funding": [ { "type": "github", @@ -5705,8 +6315,6 @@ }, "node_modules/baseline-browser-mapping": { "version": "2.9.19", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", - "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -5734,7 +6342,7 @@ }, "node_modules/bl": { "version": "4.1.0", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "buffer": "^5.5.0", @@ -5744,7 +6352,7 @@ }, "node_modules/bl/node_modules/readable-stream": { "version": "3.6.2", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -5755,6 +6363,10 @@ "node": ">= 6" } }, + "node_modules/bmp-js": { + "version": "0.1.0", + "license": "MIT" + }, "node_modules/boolbase": { "version": "1.0.0", "dev": true, @@ -5840,7 +6452,7 @@ }, "node_modules/buffer": { "version": "5.7.1", - "devOptional": true, + "dev": true, "funding": [ { "type": "github", @@ -5875,7 +6487,7 @@ }, "node_modules/cac": { "version": "6.7.14", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5962,8 +6574,6 @@ }, "node_modules/caniuse-lite": { "version": "1.0.30001769", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", - "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", "funding": [ { "type": "opencollective", @@ -6084,8 +6694,6 @@ }, "node_modules/chrono-node": { "version": "2.8.4", - "resolved": "https://registry.npmjs.org/chrono-node/-/chrono-node-2.8.4.tgz", - "integrity": "sha512-F+Rq88qF3H2dwjnFrl3TZrn5v4ZO57XxeQ+AhuL1C685So1hdUV/hT/q8Ja5UbmPYEZfx8VrxFDa72Dgldcxpg==", "license": "MIT", "dependencies": { "dayjs": "^1.10.0" @@ -6141,7 +6749,7 @@ }, "node_modules/color": { "version": "4.2.3", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1", @@ -6167,7 +6775,7 @@ }, "node_modules/color-string": { "version": "1.9.1", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "color-name": "^1.0.0", @@ -6176,7 +6784,7 @@ }, "node_modules/colorette": { "version": "2.0.20", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/commander": { @@ -6202,7 +6810,7 @@ }, "node_modules/consola": { "version": "3.4.2", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": "^14.18.0 || >=16.10.0" @@ -6398,8 +7006,6 @@ }, "node_modules/conventional-changelog-writer/node_modules/handlebars": { "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6428,14 +7034,24 @@ }, "node_modules/conventional-changelog-writer/node_modules/source-map": { "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, + "node_modules/conventional-changelog-writer/node_modules/uglify-js": { + "version": "3.19.3", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/conventional-commits-filter": { "version": "2.0.7", "dev": true, @@ -6507,8 +7123,6 @@ }, "node_modules/cross-fetch": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", "license": "MIT", "dependencies": { "node-fetch": "^2.6.12" @@ -6691,8 +7305,6 @@ }, "node_modules/data-view-buffer": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", - "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -6708,8 +7320,6 @@ }, "node_modules/data-view-byte-length": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", - "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -6725,8 +7335,6 @@ }, "node_modules/data-view-byte-offset": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", - "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -6750,8 +7358,6 @@ }, "node_modules/dayjs": { "version": "1.11.18", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz", - "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==", "license": "MIT" }, "node_modules/debug": { @@ -6805,7 +7411,7 @@ }, "node_modules/decode-bmp": { "version": "0.2.1", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@canvas/image-data": "^1.0.0", @@ -6817,7 +7423,7 @@ }, "node_modules/decode-ico": { "version": "0.4.1", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@canvas/image-data": "^1.0.0", @@ -6830,7 +7436,7 @@ }, "node_modules/decompress-response": { "version": "6.0.0", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "mimic-response": "^3.1.0" @@ -6844,7 +7450,7 @@ }, "node_modules/deep-extend": { "version": "0.6.0", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=4.0.0" @@ -6902,7 +7508,7 @@ }, "node_modules/defu": { "version": "6.1.4", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/del": { @@ -6981,7 +7587,7 @@ }, "node_modules/detect-libc": { "version": "2.0.4", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -7171,7 +7777,7 @@ }, "node_modules/end-of-stream": { "version": "1.4.5", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "once": "^1.4.0" @@ -7256,8 +7862,6 @@ }, "node_modules/es-iterator-helpers/node_modules/es-abstract": { "version": "1.24.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", - "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", "dev": true, "license": "MIT", "dependencies": { @@ -7359,8 +7963,6 @@ }, "node_modules/es-to-primitive": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", - "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", "license": "MIT", "dependencies": { "is-callable": "^1.2.7", @@ -7376,6 +7978,7 @@ }, "node_modules/esbuild": { "version": "0.21.5", + "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -7410,6 +8013,21 @@ "@esbuild/win32-x64": "0.21.5" } }, + "node_modules/esbuild/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/escalade": { "version": "3.2.0", "license": "MIT", @@ -7429,9 +8047,6 @@ }, "node_modules/eslint": { "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", "dependencies": { @@ -7526,8 +8141,6 @@ }, "node_modules/eslint-plugin-react": { "version": "7.37.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", - "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", "dev": true, "license": "MIT", "dependencies": { @@ -7609,8 +8222,6 @@ }, "node_modules/eslint-plugin-react/node_modules/resolve": { "version": "2.0.0-next.5", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", - "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", "dev": true, "license": "MIT", "dependencies": { @@ -7850,7 +8461,7 @@ }, "node_modules/expand-template": { "version": "2.0.3", - "devOptional": true, + "dev": true, "license": "(MIT OR WTFPL)", "engines": { "node": ">=6" @@ -7873,7 +8484,7 @@ }, "node_modules/fast-fifo": { "version": "1.3.2", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -8076,8 +8687,6 @@ }, "node_modules/for-each": { "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "license": "MIT", "dependencies": { "is-callable": "^1.2.7" @@ -8145,7 +8754,7 @@ }, "node_modules/fs-constants": { "version": "1.0.0", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/fs-extra": { @@ -8189,9 +8798,6 @@ }, "node_modules/fsevents": { "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, "license": "MIT", "optional": true, "os": [ @@ -8210,8 +8816,6 @@ }, "node_modules/function.prototype.name": { "version": "1.1.8", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", - "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -8358,8 +8962,6 @@ }, "node_modules/get-symbol-description": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", - "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -8451,7 +9053,7 @@ }, "node_modules/github-from-package": { "version": "0.0.0", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/glob": { @@ -8588,8 +9190,6 @@ }, "node_modules/has-bigints": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", - "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -8694,8 +9294,6 @@ }, "node_modules/html-parse-stringify": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", - "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", "license": "MIT", "dependencies": { "void-elements": "3.1.0" @@ -8759,8 +9357,6 @@ }, "node_modules/i18next": { "version": "25.8.14", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.8.14.tgz", - "integrity": "sha512-paMUYkfWJMsWPeE/Hejcw+XLhHrQPehem+4wMo+uELnvIwvCG019L9sAIljwjCmEMtFQQO3YeitJY8Kctei3iA==", "funding": [ { "type": "individual", @@ -8790,8 +9386,6 @@ }, "node_modules/i18next-browser-languagedetector": { "version": "8.2.1", - "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.1.tgz", - "integrity": "sha512-bZg8+4bdmaOiApD7N7BPT9W8MLZG+nPTOFlLiJiT8uzKXFjhxw4v2ierCXOwB5sFDMtuA5G4kgYZ0AznZxQ/cw==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.2" @@ -8799,8 +9393,6 @@ }, "node_modules/i18next-http-backend": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-3.0.2.tgz", - "integrity": "sha512-PdlvPnvIp4E1sYi46Ik4tBYh/v/NbYfFFgTjkwFl0is8A18s7/bx9aXqsrOax9WUbeNS6mD2oix7Z0yGGf6m5g==", "license": "MIT", "dependencies": { "cross-fetch": "4.0.0" @@ -8808,16 +9400,20 @@ }, "node_modules/ico-endec": { "version": "0.1.6", - "devOptional": true, + "dev": true, "license": "MPL-2.0" }, "node_modules/idb": { "version": "7.1.1", "license": "ISC" }, + "node_modules/idb-keyval": { + "version": "6.2.6", + "license": "Apache-2.0" + }, "node_modules/ieee754": { "version": "1.2.1", - "devOptional": true, + "dev": true, "funding": [ { "type": "github", @@ -8917,8 +9513,6 @@ }, "node_modules/is-array-buffer": { "version": "3.0.5", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", - "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -8938,8 +9532,6 @@ }, "node_modules/is-async-function": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", - "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", "license": "MIT", "dependencies": { "async-function": "^1.0.0", @@ -8957,8 +9549,6 @@ }, "node_modules/is-bigint": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", - "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", "license": "MIT", "dependencies": { "has-bigints": "^1.0.2" @@ -8983,8 +9573,6 @@ }, "node_modules/is-boolean-object": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", - "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -8999,8 +9587,6 @@ }, "node_modules/is-callable": { "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -9024,8 +9610,6 @@ }, "node_modules/is-data-view": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", - "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -9041,8 +9625,6 @@ }, "node_modules/is-date-object": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", - "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -9079,8 +9661,6 @@ }, "node_modules/is-finalizationregistry": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", - "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", "license": "MIT", "dependencies": { "call-bound": "^1.0.3" @@ -9101,8 +9681,6 @@ }, "node_modules/is-generator-function": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", - "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -9130,8 +9708,6 @@ }, "node_modules/is-map": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -9146,8 +9722,6 @@ }, "node_modules/is-negative-zero": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -9166,8 +9740,6 @@ }, "node_modules/is-number-object": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", - "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -9214,8 +9786,6 @@ }, "node_modules/is-regex": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -9249,8 +9819,6 @@ }, "node_modules/is-set": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -9261,8 +9829,6 @@ }, "node_modules/is-shared-array-buffer": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", - "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", "license": "MIT", "dependencies": { "call-bound": "^1.0.3" @@ -9300,8 +9866,6 @@ }, "node_modules/is-symbol": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", - "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -9328,8 +9892,6 @@ }, "node_modules/is-typed-array": { "version": "1.1.15", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "license": "MIT", "dependencies": { "which-typed-array": "^1.1.16" @@ -9341,10 +9903,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-url": { + "version": "1.2.4", + "license": "MIT" + }, "node_modules/is-weakmap": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -9355,8 +9919,6 @@ }, "node_modules/is-weakref": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", - "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", "license": "MIT", "dependencies": { "call-bound": "^1.0.3" @@ -9370,8 +9932,6 @@ }, "node_modules/is-weakset": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", - "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -9451,7 +10011,7 @@ }, "node_modules/jiti": { "version": "1.21.7", - "devOptional": true, + "dev": true, "license": "MIT", "bin": { "jiti": "bin/jiti.js" @@ -9725,8 +10285,6 @@ }, "node_modules/lodash": { "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, "node_modules/lodash.clonedeep": { @@ -10034,7 +10592,7 @@ }, "node_modules/mimic-response": { "version": "3.1.0", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -10067,7 +10625,7 @@ }, "node_modules/minimist": { "version": "1.2.8", - "devOptional": true, + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -10130,7 +10688,7 @@ }, "node_modules/mkdirp-classic": { "version": "0.5.3", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/modify-values": { @@ -10143,8 +10701,6 @@ }, "node_modules/moment": { "version": "2.30.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", - "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", "license": "MIT", "engines": { "node": "*" @@ -10170,6 +10726,7 @@ }, "node_modules/nanoid": { "version": "3.3.11", + "dev": true, "funding": [ { "type": "github", @@ -10186,7 +10743,7 @@ }, "node_modules/napi-build-utils": { "version": "2.0.0", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/native-run": { @@ -10256,7 +10813,7 @@ }, "node_modules/node-abi": { "version": "3.77.0", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "semver": "^7.3.5" @@ -10267,7 +10824,7 @@ }, "node_modules/node-addon-api": { "version": "6.1.0", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/node-fetch": { @@ -10452,8 +11009,6 @@ }, "node_modules/object.fromentries/node_modules/es-abstract": { "version": "1.24.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", - "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", "dev": true, "license": "MIT", "dependencies": { @@ -10559,6 +11114,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/opencollective-postinstall": { + "version": "2.0.3", + "license": "MIT", + "bin": { + "opencollective-postinstall": "index.js" + } + }, "node_modules/optionator": { "version": "0.9.4", "dev": true, @@ -10577,8 +11139,6 @@ }, "node_modules/own-keys": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", - "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", "license": "MIT", "dependencies": { "get-intrinsic": "^1.2.6", @@ -10819,8 +11379,6 @@ }, "node_modules/possible-typed-array-names": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -10828,6 +11386,7 @@ }, "node_modules/postcss": { "version": "8.5.6", + "dev": true, "funding": [ { "type": "opencollective", @@ -10922,8 +11481,6 @@ }, "node_modules/postcss-load-config/node_modules/yaml": { "version": "2.8.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", "dev": true, "license": "ISC", "bin": { @@ -10976,7 +11533,7 @@ }, "node_modules/prebuild-install": { "version": "7.1.3", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "detect-libc": "^2.0.0", @@ -11001,12 +11558,12 @@ }, "node_modules/prebuild-install/node_modules/chownr": { "version": "1.1.4", - "devOptional": true, + "dev": true, "license": "ISC" }, "node_modules/prebuild-install/node_modules/readable-stream": { "version": "3.6.2", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -11019,7 +11576,7 @@ }, "node_modules/prebuild-install/node_modules/tar-fs": { "version": "2.1.3", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "chownr": "^1.1.1", @@ -11030,7 +11587,7 @@ }, "node_modules/prebuild-install/node_modules/tar-stream": { "version": "2.2.0", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "bl": "^4.0.3", @@ -11053,8 +11610,6 @@ }, "node_modules/prettier": { "version": "3.6.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", - "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", "bin": { @@ -11200,7 +11755,7 @@ }, "node_modules/pump": { "version": "3.0.3", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", @@ -11392,8 +11947,6 @@ }, "node_modules/quill/node_modules/lodash-es": { "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", "license": "MIT" }, "node_modules/quilljs-markdown": { @@ -11406,8 +11959,6 @@ }, "node_modules/quilljs-markdown/node_modules/core-js": { "version": "3.45.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.45.1.tgz", - "integrity": "sha512-L4NPsJlCfZsPeXukyzHFlg/i7IIVwHSItR0wg0FLNqYClJ4MQYTYLbC7EkjKYRLZF2iof2MUgN0EGy7MdQFChg==", "hasInstallScript": true, "license": "MIT", "funding": { @@ -11428,7 +11979,7 @@ }, "node_modules/rc": { "version": "1.2.8", - "devOptional": true, + "dev": true, "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", "dependencies": { "deep-extend": "^0.6.0", @@ -11442,12 +11993,12 @@ }, "node_modules/rc/node_modules/ini": { "version": "1.3.8", - "devOptional": true, + "dev": true, "license": "ISC" }, "node_modules/rc/node_modules/strip-json-comments": { "version": "2.0.1", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -11488,8 +12039,6 @@ }, "node_modules/react-dom": { "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", "dependencies": { "loose-envify": "^1.1.0", @@ -11513,8 +12062,6 @@ }, "node_modules/react-i18next": { "version": "16.5.6", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-16.5.6.tgz", - "integrity": "sha512-Ua7V2/efA88ido7KyK51fb8Ki8M/sRfW8LR/rZ/9ZKr2luhuTI7kwYZN5agT1rWG7aYm5G0RYE/6JR8KJoCMDw==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.28.4", @@ -11593,8 +12140,6 @@ }, "node_modules/react-router-dom/node_modules/@remix-run/router": { "version": "1.23.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", - "integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==", "license": "MIT", "engines": { "node": ">=14.0.0" @@ -11602,8 +12147,6 @@ }, "node_modules/react-router/node_modules/@remix-run/router": { "version": "1.23.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", - "integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==", "license": "MIT", "engines": { "node": ">=14.0.0" @@ -11810,8 +12353,6 @@ }, "node_modules/recharts": { "version": "2.15.4", - "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz", - "integrity": "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==", "license": "MIT", "dependencies": { "clsx": "^2.0.0", @@ -11874,8 +12415,6 @@ }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", - "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -11896,8 +12435,6 @@ }, "node_modules/reflect.getprototypeof/node_modules/es-abstract": { "version": "1.24.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", - "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.2", @@ -12010,8 +12547,6 @@ }, "node_modules/regexpu-core/node_modules/regenerate-unicode-properties": { "version": "10.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", - "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", "license": "MIT", "dependencies": { "regenerate": "^1.4.2" @@ -12363,8 +12898,7 @@ }, "node_modules/rollup": { "version": "4.50.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.50.0.tgz", - "integrity": "sha512-/Zl4D8zPifNmyGzJS+3kVoyXeDeT/GrsJM94sACNg9RtUE0hrHa1bNPtRSrfHTMH5HjRzce6K7rlTh3Khiw+pw==", + "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.8" @@ -12401,97 +12935,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/rollup/node_modules/@rollup/rollup-darwin-x64": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.50.0.tgz", - "integrity": "sha512-cQp/WG8HE7BCGyFVuzUg0FNmupxC+EPZEwWu2FCGGw5WDT1o2/YlENbm5e9SMvfDFR6FRhVCBePLqj0o8MN7Vw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/rollup/node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.50.0.tgz", - "integrity": "sha512-1EhkSvUQXJsIhk4msxP5nNAUWoB4MFDHhtc4gAYvnqoHlaL9V3F37pNHabndawsfy/Tp7BPiy/aSa6XBYbaD1g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/rollup/node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.50.0.tgz", - "integrity": "sha512-EtBDIZuDtVg75xIPIK1l5vCXNNCIRM0OBPUG+tbApDuJAy9mKago6QxX+tfMzbCI6tXEhMuZuN1+CU8iDW+0UQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.50.0.tgz", - "integrity": "sha512-8PrJJA7/VU8ToHVEPu14FzuSAqVKyo5gg/J8xUerMbyNkWkO9j2ExBho/68RnJsMGNJq4zH114iAttgm7BZVkA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/rollup/node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.50.0.tgz", - "integrity": "sha512-SkE6YQp+CzpyOrbw7Oc4MgXFvTw2UIBElvAvLCo230pyxOLmYwRPwZ/L5lBe/VW/qT1ZgND9wJfOsdy0XptRvw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/rollup/node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.50.0.tgz", - "integrity": "sha512-q7cIIdFvWQoaCbLDUyUc8YfR3Jh2xx3unO8Dn6/TTogKjfwrax9SyfmGGK6cQhKtjePI7jRfd7iRYcxYs93esg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/rollup/node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.50.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.0.tgz", - "integrity": "sha512-xMmiWRR8sp72Zqwjgtf3QbZfF1wdh8X2ABu3EaozvZcyHJeU0r+XAnXdKgs4cCAp6ORoYoCygipYP1mjmbjrsg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/run-parallel": { "version": "1.2.0", "dev": true, @@ -12541,8 +12984,6 @@ }, "node_modules/safe-push-apply": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", - "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -12557,14 +12998,10 @@ }, "node_modules/safe-push-apply/node_modules/isarray": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", "license": "MIT" }, "node_modules/safe-regex-test": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", - "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -12592,7 +13029,7 @@ }, "node_modules/semver": { "version": "7.7.2", - "devOptional": true, + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -12642,8 +13079,6 @@ }, "node_modules/set-proto": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", - "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -12660,7 +13095,7 @@ }, "node_modules/sharp": { "version": "0.32.6", - "devOptional": true, + "dev": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -12682,7 +13117,7 @@ }, "node_modules/sharp-ico": { "version": "0.1.5", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "decode-ico": "*", @@ -12780,7 +13215,7 @@ }, "node_modules/simple-concat": { "version": "1.0.1", - "devOptional": true, + "dev": true, "funding": [ { "type": "github", @@ -12799,7 +13234,7 @@ }, "node_modules/simple-get": { "version": "4.0.1", - "devOptional": true, + "dev": true, "funding": [ { "type": "github", @@ -12844,7 +13279,7 @@ }, "node_modules/simple-swizzle": { "version": "0.2.2", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "is-arrayish": "^0.3.1" @@ -12852,7 +13287,7 @@ }, "node_modules/simple-swizzle/node_modules/is-arrayish": { "version": "0.3.2", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/sisteransi": { @@ -12897,6 +13332,7 @@ }, "node_modules/source-map-js": { "version": "1.2.1", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -12994,8 +13430,6 @@ }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", - "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -13015,7 +13449,7 @@ }, "node_modules/streamx": { "version": "2.22.1", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "fast-fifo": "^1.3.2", @@ -13025,6 +13459,12 @@ "bare-events": "^2.2.0" } }, + "node_modules/streamx/node_modules/bare-events": { + "version": "2.6.1", + "dev": true, + "license": "Apache-2.0", + "optional": true + }, "node_modules/string_decoder": { "version": "1.1.1", "license": "MIT", @@ -13085,8 +13525,6 @@ }, "node_modules/string.prototype.matchall/node_modules/es-abstract": { "version": "1.24.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", - "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.2", @@ -13162,8 +13600,6 @@ }, "node_modules/string.prototype.repeat/node_modules/es-abstract": { "version": "1.24.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", - "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", "dev": true, "license": "MIT", "dependencies": { @@ -13231,8 +13667,6 @@ }, "node_modules/string.prototype.trim": { "version": "1.2.10", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", - "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -13252,8 +13686,6 @@ }, "node_modules/string.prototype.trim/node_modules/es-abstract": { "version": "1.24.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", - "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.2", @@ -13320,8 +13752,6 @@ }, "node_modules/string.prototype.trimend": { "version": "1.0.9", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", - "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -13338,8 +13768,6 @@ }, "node_modules/string.prototype.trimstart": { "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", - "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -13437,8 +13865,6 @@ }, "node_modules/sucrase": { "version": "3.35.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", - "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", "dev": true, "license": "MIT", "dependencies": { @@ -13458,10 +13884,17 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/sucrase/node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/sucrase/node_modules/commander": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", "dev": true, "license": "MIT", "engines": { @@ -13470,8 +13903,6 @@ }, "node_modules/sucrase/node_modules/glob": { "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "license": "ISC", "dependencies": { @@ -13491,8 +13922,6 @@ }, "node_modules/sucrase/node_modules/jackspeak": { "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -13507,15 +13936,11 @@ }, "node_modules/sucrase/node_modules/lru-cache": { "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, "license": "ISC" }, "node_modules/sucrase/node_modules/path-scurry": { "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -13580,8 +14005,6 @@ }, "node_modules/tailwindcss": { "version": "3.4.17", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", - "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", "dev": true, "license": "MIT", "dependencies": { @@ -13634,7 +14057,7 @@ }, "node_modules/tar-fs": { "version": "3.1.0", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "pump": "^3.0.0", @@ -13645,9 +14068,76 @@ "bare-path": "^3.0.0" } }, + "node_modules/tar-fs/node_modules/bare-events": { + "version": "2.6.1", + "dev": true, + "license": "Apache-2.0", + "optional": true + }, + "node_modules/tar-fs/node_modules/bare-fs": { + "version": "4.2.3", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/tar-fs/node_modules/bare-os": { + "version": "3.6.2", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/tar-fs/node_modules/bare-path": { + "version": "3.0.0", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/tar-fs/node_modules/bare-stream": { + "version": "2.7.0", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "streamx": "^2.21.0" + }, + "peerDependencies": { + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, "node_modules/tar-stream": { "version": "3.1.7", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "b4a": "^1.6.4", @@ -13701,8 +14191,6 @@ }, "node_modules/terser": { "version": "5.44.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz", - "integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==", "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -13719,13 +14207,31 @@ }, "node_modules/terser/node_modules/commander": { "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "license": "MIT" }, + "node_modules/tesseract.js": { + "version": "7.0.0", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "bmp-js": "^0.1.0", + "idb-keyval": "^6.2.0", + "is-url": "^1.2.4", + "node-fetch": "^2.6.9", + "opencollective-postinstall": "^2.0.3", + "regenerator-runtime": "^0.13.3", + "tesseract.js-core": "^7.0.0", + "wasm-feature-detect": "^1.8.0", + "zlibjs": "^0.3.1" + } + }, + "node_modules/tesseract.js-core": { + "version": "7.0.0", + "license": "Apache-2.0" + }, "node_modules/text-decoder": { "version": "1.2.3", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "dependencies": { "b4a": "^1.6.4" @@ -13842,7 +14348,7 @@ }, "node_modules/to-data-view": { "version": "1.1.0", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/to-regex-range": { @@ -13953,7 +14459,7 @@ }, "node_modules/tunnel-agent": { "version": "0.6.0", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "dependencies": { "safe-buffer": "^5.0.1" @@ -13986,8 +14492,6 @@ }, "node_modules/typed-array-buffer": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", - "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -14000,8 +14504,6 @@ }, "node_modules/typed-array-byte-length": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", - "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -14019,8 +14521,6 @@ }, "node_modules/typed-array-byte-offset": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", - "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", @@ -14040,8 +14540,6 @@ }, "node_modules/typed-array-length": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", - "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -14058,25 +14556,8 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/typescript": { - "version": "5.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", - "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", - "devOptional": true, - "license": "Apache-2.0", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, "node_modules/unbox-primitive": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", - "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -14093,7 +14574,7 @@ }, "node_modules/unconfig": { "version": "0.3.13", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@antfu/utils": "^0.7.7", @@ -14106,7 +14587,7 @@ }, "node_modules/undici-types": { "version": "7.10.0", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { @@ -14212,8 +14693,6 @@ }, "node_modules/use-sync-external-store": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", - "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", "license": "MIT", "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -14251,8 +14730,6 @@ }, "node_modules/victory-vendor": { "version": "36.9.2", - "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", - "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", "license": "MIT AND ISC", "dependencies": { "@types/d3-array": "^3.0.3", @@ -14273,8 +14750,7 @@ }, "node_modules/vite": { "version": "5.4.19", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", - "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", + "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.21.3", @@ -14379,8 +14855,6 @@ }, "node_modules/void-elements": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", - "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -14393,6 +14867,10 @@ "loose-envify": "^1.0.0" } }, + "node_modules/wasm-feature-detect": { + "version": "1.8.0", + "license": "Apache-2.0" + }, "node_modules/webidl-conversions": { "version": "3.0.1", "license": "BSD-2-Clause" @@ -14421,8 +14899,6 @@ }, "node_modules/which-boxed-primitive": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", - "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", "license": "MIT", "dependencies": { "is-bigint": "^1.1.0", @@ -14440,8 +14916,6 @@ }, "node_modules/which-builtin-type": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", - "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -14467,14 +14941,10 @@ }, "node_modules/which-builtin-type/node_modules/isarray": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", "license": "MIT" }, "node_modules/which-collection": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", - "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", "license": "MIT", "dependencies": { "is-map": "^2.0.3", @@ -14495,8 +14965,6 @@ }, "node_modules/which-typed-array": { "version": "1.1.19", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", @@ -14665,8 +15133,6 @@ }, "node_modules/workbox-build/node_modules/@surma/rollup-plugin-off-main-thread": { "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", - "integrity": "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==", "license": "Apache-2.0", "dependencies": { "ejs": "^3.1.6", @@ -14681,8 +15147,6 @@ }, "node_modules/workbox-build/node_modules/ajv": { "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -14705,8 +15169,6 @@ }, "node_modules/workbox-build/node_modules/common-tags": { "version": "1.8.2", - "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", - "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", "license": "MIT", "engines": { "node": ">=4.0.0" @@ -14773,8 +15235,6 @@ }, "node_modules/workbox-build/node_modules/rollup": { "version": "2.79.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", - "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", "license": "MIT", "bin": { "rollup": "dist/bin/rollup" @@ -15124,6 +15584,13 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zlibjs": { + "version": "0.3.1", + "license": "MIT", + "engines": { + "node": "*" + } } } } diff --git a/package.json b/package.json index 6aed4fc..2410be7 100644 --- a/package.json +++ b/package.json @@ -37,39 +37,42 @@ "bump:patch": "node bump-version.js patch" }, "dependencies": { - "@capacitor-community/sqlite": "^7.0.0", - "@capacitor/android": "^7.0.0", - "@capacitor/app": "^7.0.0", - "@capacitor/browser": "^7.0.2", - "@capacitor/core": "^7.0.0", - "@capacitor/device": "^7.0.0", - "@capacitor/ios": "^7.0.0", - "@capacitor/local-notifications": "^7.0.0", - "@capacitor/network": "^7.0.1", - "@capacitor/preferences": "^7.0.0", - "@capacitor/push-notifications": "^7.0.0", - "@capacitor/status-bar": "^7.0.0", - "@capgo/capacitor-nfc": "^7.2.0", - "@capgo/capacitor-social-login": "^7.5.3", + "@capacitor-community/sqlite": "^8.0.0", + "@capacitor/android": "^8.0.0", + "@capacitor/app": "^8.0.0", + "@capacitor/browser": "^8.0.0", + "@capacitor/core": "^8.0.0", + "@capacitor/device": "^8.0.0", + "@capacitor/ios": "^8.0.0", + "@capacitor/local-llm": "^1.0.0", + "@capacitor/local-notifications": "^8.0.0", + "@capacitor/network": "^8.0.0", + "@capacitor/preferences": "^8.0.0", + "@capacitor/push-notifications": "^8.0.0", + "@capacitor/status-bar": "^8.0.0", + "@capgo/capacitor-document-scanner": "^8.4.0", + "@capgo/capacitor-nfc": "^8.0.0", + "@capgo/capacitor-social-login": "^8.0.0", "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", "@emotion/react": "^11.11.3", "@emotion/styled": "^11.11.0", "@hello-pangea/dnd": "^18.0.1", + "@jcesarmobile/capacitor-ocr": "^0.3.0", "@meauxt/react-swipeable-list": "^1.0.0", "@mui/icons-material": "^5.16.13", "@mui/joy": "^5.0.0-beta.20", "@mui/material": "^5.15.2", "@openreplay/tracker": "^14.0.4", - "@revenuecat/purchases-capacitor": "^11.1.0", - "@revenuecat/purchases-capacitor-ui": "^11.1.0", + "@revenuecat/purchases-capacitor": "^12.0.0", + "@revenuecat/purchases-capacitor-ui": "^12.0.0", "@swc/core": "^1.12.5", "@tanstack/react-query": "^5.17.0", "aos": "^2.3.4", "browser-image-compression": "^2.0.2", "caniuse-lite": "^1.0.30001769", - "capacitor-plugin-safe-area": "^4.0.0", + "capacitor-plugin-safe-area": "^5.0.0", "chrono-node": "^2.7.7", "dotenv": "^16.4.5", "esm": "^3.2.25", @@ -95,11 +98,12 @@ "reactjs-social-login": "^2.6.3", "recharts": "^2.15.0", "reusify": "^1.0.4", + "tesseract.js": "^7.0.0", "vite-plugin-pwa": "^0.20.0" }, "devDependencies": { "@capacitor/assets": "^3.0.5", - "@capacitor/cli": "^7.0.0", + "@capacitor/cli": "^8.0.0", "@tanstack/eslint-plugin-query": "^5.14.6", "@types/react": "^18.2.43", "@types/react-dom": "^18.2.17", @@ -123,6 +127,10 @@ "prettier": "^3.1.1", "prettier-plugin-tailwindcss": "^0.5.10", "tailwindcss": "^3.4.0", + "typescript": "^5.8.0", "vite": "^5.2.13" + }, + "allowScripts": { + "tesseract.js@7.0.0": true } } diff --git a/src/components/UserProfileAvatar.jsx b/src/components/UserProfileAvatar.jsx index 0cfd165..e306c99 100644 --- a/src/components/UserProfileAvatar.jsx +++ b/src/components/UserProfileAvatar.jsx @@ -32,7 +32,7 @@ import { useImpersonateUser } from '../contexts/ImpersonateUserContext' import useStickyState from '../hooks/useStickyState' import { useCircleMembers, useUserProfile } from '../queries/UserQueries' import { apiClient } from '../utils/ApiClient' -import { isPlusAccount } from '../utils/Helpers' +import { isPlusAccount, resolvePhotoURL } from '../utils/Helpers' import UserModal from '../views/Modals/Inputs/UserModal' import SubscriptionModal from './SubscriptionModal' @@ -116,7 +116,7 @@ const UserProfileAvatar = () => { {isImpersonating ? ( { }} /> { ) : ( { + {title && ( {title} diff --git a/src/hooks/useDocumentScanner.js b/src/hooks/useDocumentScanner.js new file mode 100644 index 0000000..ff66e9b --- /dev/null +++ b/src/hooks/useDocumentScanner.js @@ -0,0 +1,50 @@ +import { Capacitor } from '@capacitor/core' + +/** + * Normalizes a raw image string from the native document scanner into a + * format that can be used as an src and passed to Tesseract.js. + * + * Android returns file:// or absolute paths → convert via Capacitor.convertFileSrc + * iOS returns raw base64 (no data: prefix) → prepend the data URI scheme + */ +function normalizeScannedImage(raw) { + if (!raw) return null + if (raw.startsWith('data:')) return raw + if (raw.startsWith('http://') || raw.startsWith('https://') || raw.startsWith('content://')) return raw + if (raw.startsWith('/') || raw.startsWith('file://')) return Capacitor.convertFileSrc(raw) + // iOS base64 without prefix + return `data:image/jpeg;base64,${raw}` +} + +/** + * Hook for native document scanning via @capgo/capacitor-document-scanner. + * + * On native: opens the OS document scanner (edge detection, perspective correction). + * On web: `scanDocument` returns null — callers should fall back to their own camera UI. + */ +export function useDocumentScanner() { + const isNativeScanner = Capacitor.isNativePlatform() + + const scanDocument = async ({ maxDocuments = 1, quality = 90, letUserAdjustCrop = true } = {}) => { + if (!isNativeScanner) return { image: null, cancelled: false } + + try { + const { DocumentScanner } = await import('@capgo/capacitor-document-scanner') + const { scannedImages } = await DocumentScanner.scanDocument({ + croppedImageQuality: quality, + maxNumDocuments: maxDocuments, + letUserAdjustCrop, + }) + + if (!scannedImages?.length) return { image: null, cancelled: true } + + const normalized = normalizeScannedImage(scannedImages[0]) + return { image: normalized, cancelled: false } + } catch (e) { + console.error('[DocumentScanner] scan failed:', e) + return { image: null, cancelled: false, error: e.message } + } + } + + return { isNativeScanner, scanDocument } +} diff --git a/src/hooks/useFileUpload.js b/src/hooks/useFileUpload.js index 1aba6f9..e44329e 100644 --- a/src/hooks/useFileUpload.js +++ b/src/hooks/useFileUpload.js @@ -5,7 +5,7 @@ import { useNotification } from '../service/NotificationProvider' import { apiClient } from '../utils/ApiClient' import { isPlusAccount, resolvePhotoURL } from '../utils/Helpers' -export const useFileUpload = ({ entityType = 'chore_attachment', entityId } = {}) => { +export const useFileUpload = ({ entityType = 'chore_attachment', entityId, draftId } = {}) => { const { showError } = useNotification() const { data: userProfile } = useUserProfile() @@ -38,7 +38,8 @@ export const useFileUpload = ({ entityType = 'chore_attachment', entityId } = {} const formData = new FormData() formData.append('file', compressedJpegFile) formData.append('entityType', entityType) - if (entityId) formData.append('entityId', entityId) + if (entityId) formData.append('entityId', String(entityId)) + if (draftId) formData.append('draftId', draftId) const response = await apiClient.upload('/assets/chore', formData) @@ -84,7 +85,7 @@ export const useFileUpload = ({ entityType = 'chore_attachment', entityId } = {} return null } }, - [entityType, entityId, showError, userProfile], + [entityType, entityId, draftId, showError, userProfile], ) return { uploadFile, isPlus: isPlusAccount(userProfile) } diff --git a/src/queries/ChoreQueries.jsx b/src/queries/ChoreQueries.jsx index ec4883d..20186dc 100644 --- a/src/queries/ChoreQueries.jsx +++ b/src/queries/ChoreQueries.jsx @@ -8,6 +8,7 @@ import { CreateChore, DeleteChore, DeleteChoreHistory, + GetChoreAttachments, GetChoreByID, GetChoreDetailById, GetChoreHistory, @@ -696,3 +697,19 @@ export const useRejectChore = () => { }, }) } + +export const useChoreAttachments = (choreId, hasAttachments = true) => { + return useQuery({ + queryKey: ['choreAttachments', choreId], + queryFn: async () => { + const response = await GetChoreAttachments(choreId) + if (response && response.ok) { + return await response.json() + } + throw new Error('Failed to fetch attachments') + }, + enabled: !!choreId && hasAttachments, + staleTime: 10 * 60 * 1000, + gcTime: 15 * 60 * 1000, + }) +} diff --git a/src/service/AIPromptCache.js b/src/service/AIPromptCache.js new file mode 100644 index 0000000..d86393e --- /dev/null +++ b/src/service/AIPromptCache.js @@ -0,0 +1,72 @@ +const ENABLED_KEY = 'ai_prompt_cache_enabled' +const ENTRY_PREFIX = 'ai_prompt_cache_' +const INDEX_KEY = 'ai_prompt_cache_index' + +function djb2(str) { + let hash = 5381 + for (let i = 0; i < str.length; i++) { + hash = ((hash << 5) + hash) ^ str.charCodeAt(i) + hash = hash >>> 0 + } + return hash.toString(36) +} + +export function isCacheEnabled() { + try { + return localStorage.getItem(ENABLED_KEY) === 'true' + } catch { + return false + } +} + +export function setCacheEnabled(enabled) { + try { + localStorage.setItem(ENABLED_KEY, String(enabled)) + } catch { /* ignore */ } +} + +export function hashContent(content) { + return djb2(typeof content === 'string' ? content : JSON.stringify(content)) +} + +function getIndex() { + try { + return JSON.parse(localStorage.getItem(INDEX_KEY) || '[]') + } catch { + return [] + } +} + +export function getCached(hash) { + if (!isCacheEnabled()) return null + try { + const raw = localStorage.getItem(ENTRY_PREFIX + hash) + return raw ? JSON.parse(raw) : null + } catch { + return null + } +} + +export function setCached(hash, value) { + if (!isCacheEnabled()) return + try { + localStorage.setItem(ENTRY_PREFIX + hash, JSON.stringify(value)) + const index = getIndex() + if (!index.includes(hash)) { + index.push(hash) + localStorage.setItem(INDEX_KEY, JSON.stringify(index)) + } + } catch { /* storage full, ignore */ } +} + +export function getCacheStats() { + return { count: getIndex().length } +} + +export function clearCache() { + const index = getIndex() + index.forEach(h => { + try { localStorage.removeItem(ENTRY_PREFIX + h) } catch { /* ignore */ } + }) + try { localStorage.removeItem(INDEX_KEY) } catch { /* ignore */ } +} diff --git a/src/service/LocalAIService.js b/src/service/LocalAIService.js new file mode 100644 index 0000000..6b3e1dd --- /dev/null +++ b/src/service/LocalAIService.js @@ -0,0 +1,141 @@ +import { Capacitor } from '@capacitor/core' +import { getCached, hashContent, setCached } from './AIPromptCache' + +// Native-only local AI service using @capacitor/local-llm. +// On web, all methods return 'unavailable' / null — no WebLLM. +class LocalAIService { + constructor() { + this._availability = null + this._sessionId = 'donetick-summary' + this._warmedUp = false + } + + get isNative() { + return Capacitor.isNativePlatform() + } + + async checkAvailability() { + if (!this.isNative) { + this._availability = 'unavailable' + return 'unavailable' + } + try { + const { LocalLLM } = await import('@capacitor/local-llm') + const { status } = await LocalLLM.systemAvailability() + this._availability = status + return status + } catch (e) { + this._availability = 'unavailable' + return 'unavailable' + } + } + + async getStatus() { + if (this._availability !== null) return this._availability + return this.checkAvailability() + } + + async isAvailable() { + return (await this.getStatus()) === 'available' + } + + resetAvailability() { + this._availability = null + } + + async download(onStatusChange) { + if (!this.isNative) return + try { + const { LocalLLM } = await import('@capacitor/local-llm') + if (onStatusChange) { + LocalLLM.addListener('systemAvailabilityChange', ({ status }) => { + this._availability = status + onStatusChange(status) + }) + } + await LocalLLM.download() + } catch { + // download not available on iOS, ignore + } + } + + async warmup() { + if (this._warmedUp || !this.isNative) return + try { + const { LocalLLM } = await import('@capacitor/local-llm') + await LocalLLM.warmup({ sessionId: this._sessionId }) + this._warmedUp = true + } catch { + // non-fatal + } + } + + async _nativePrompt(text) { + await this.warmup() + try { + const { LocalLLM } = await import('@capacitor/local-llm') + const { text: out } = await LocalLLM.prompt({ prompt: text, sessionId: this._sessionId }) + return out?.trim() || null + } finally { + try { + const { LocalLLM } = await import('@capacitor/local-llm') + await LocalLLM.endSession({ sessionId: this._sessionId }) + this._warmedUp = false + } catch { /* ignore */ } + } + } + + // Plain chat — no tools. Returns answer string or null. + async plainChat(messages) { + const available = await this.isAvailable() + if (!available) return null + + const cacheHash = hashContent(['plain', ...messages]) + const cached = getCached(cacheHash) + if (cached) return cached + + if (!this.isNative) return null + + try { + const systemMsg = messages.find(m => m.role === 'system')?.content || '' + const userMsg = messages.find(m => m.role === 'user')?.content || '' + const result = await this._nativePrompt(`${systemMsg}\n\nUser: ${userMsg}\nAssistant:`) + if (result) setCached(cacheHash, result) + return result + } catch (e) { + console.error('[LocalAI] plainChat() failed:', e) + return null + } + } + + // Returns the summary string or null if LLM is unavailable + async summarize(prompt) { + const available = await this.isAvailable() + if (!available) return null + + const cacheHash = hashContent(prompt) + const cached = getCached(cacheHash) + if (cached) return cached + + if (!this.isNative) return null + + try { + await this.warmup() + const { LocalLLM } = await import('@capacitor/local-llm') + const { text } = await LocalLLM.prompt({ prompt, sessionId: this._sessionId }) + const result = text?.trim() || null + if (result) setCached(cacheHash, result) + return result + } catch { + return null + } finally { + try { + const { LocalLLM } = await import('@capacitor/local-llm') + await LocalLLM.endSession({ sessionId: this._sessionId }) + this._warmedUp = false + } catch { /* ignore */ } + } + } +} + +export const localAIService = new LocalAIService() diff --git a/src/utils/Fetcher.jsx b/src/utils/Fetcher.jsx index 167394a..0e3caae 100644 --- a/src/utils/Fetcher.jsx +++ b/src/utils/Fetcher.jsx @@ -728,6 +728,30 @@ const DeleteUser = (password, confirmation, transferOptions = []) => { }) } +const UploadChoreAttachment = (file, entityType, { entityId, draftId } = {}) => { + const formData = new FormData() + formData.append('file', file) + formData.append('entityType', entityType) + if (entityId != null) formData.append('entityId', String(entityId)) + if (draftId != null) formData.append('draftId', draftId) + return apiClient.upload('/assets/chore', formData) +} + +const GetChoreAttachments = choreId => { + return Fetch(`/chores/${choreId}/attachments`, { + method: 'GET', + headers: HEADERS(), + }) +} + +const DeleteChoreAttachment = (choreId, filePath) => { + return Fetch(`/chores/${choreId}/attachments`, { + method: 'DELETE', + headers: HEADERS(), + body: JSON.stringify({ file_path: filePath }), + }) +} + const CreateBackup = (encryptionKey, includeAssets = true, backupName = '') => { return Fetch(`/backup/create`, { method: 'POST', @@ -933,6 +957,9 @@ const TrackFilterUsage = id => { export { AcceptCircleMemberRequest, + DeleteChoreAttachment, + GetChoreAttachments, + UploadChoreAttachment, ApproveChore, ArchiveChore, CancelSubscription, diff --git a/src/utils/Helpers.jsx b/src/utils/Helpers.jsx index fb1ef4c..b276778 100644 --- a/src/utils/Helpers.jsx +++ b/src/utils/Helpers.jsx @@ -10,9 +10,114 @@ const resolvePhotoURL = url => { if (url.startsWith('http') || url.startsWith('https')) { return url } - if (url.startsWith('assets')) { - return apiClient.getAssetURL(url) - } - return url + return apiClient.getAssetURL(url) } -export { isPlusAccount, resolvePhotoURL } + +// Detect cloud storage pre-signed URLs (S3, GCS, Azure) that carry expiry params. +const isCloudSignedUrl = url => { + if (!url) return false + try { + return ( + url.includes('X-Amz-Signature') || + url.includes('X-Amz-Expires') || + url.includes('X-Goog-Expires') || + url.includes('expires') + ) + } catch(e) { + return false + } +} + +// Extract the storage key from a cloud signed URL so we can route it through +// the backend proxy (which re-signs on every request and never expires). +// +// Handles: +// Virtual-hosted S3: https://{bucket}.s3[.region].amazonaws.com/{key}?... +// Path-style S3: https://s3[.region].amazonaws.com/{bucket}/{key}?... +// Cloudflare R2: https://{bucket}.{accountid}.r2.cloudflarestorage.com/{key}?... +// GCS: https://storage.googleapis.com/{bucket}/{key}?... +// Azure Blob: https://{account}.blob.core.windows.net/{container}/{blob}?... +// +// The app stores files under an "assets/" prefix in the bucket but the backend +// proxy already mounts at /assets/, so we strip that leading segment when present. +const extractStorageKey = url => { + try { + const u = new URL(url) + const host = u.hostname + const rawPath = u.pathname.replace(/^\//, '') + + let key + + if (host.endsWith('.r2.cloudflarestorage.com')) { + // Virtual-hosted R2: bucket is in the host, key is the full path + key = rawPath + } else if (host.endsWith('.amazonaws.com')) { + if (host.startsWith('s3') || host.includes('.s3.')) { + // Path-style S3: first segment is the bucket — strip it + key = rawPath.split('/').slice(1).join('/') + } else { + // Virtual-hosted S3: bucket is in the host, path is the key + key = rawPath + } + } else if (host === 'storage.googleapis.com') { + // First segment is the bucket + key = rawPath.split('/').slice(1).join('/') + } else if (host.endsWith('.blob.core.windows.net')) { + // First segment is the container name + key = rawPath.split('/').slice(1).join('/') + } else { + key = rawPath + } + + // The bucket stores files under an "assets/" prefix; the backend /assets/ + // endpoint already adds that prefix, so strip it to avoid duplication. + if (key.startsWith('assets/')) { + key = key.slice('assets/'.length) + } + + return key || null + } catch { + return null + } +} + +// Scan an HTML string for tags whose src is a cloud signed URL and +// replace them with backend proxy URLs (which generate fresh signed URLs on +// each request). Returns the patched HTML, or the original if nothing changed. +const refreshSignedUrlsInHtml = html => { + if (!html) return html + if ( + !html.includes('dt-data-path') && + !html.includes('X-Amz-') && + !html.includes('X-Goog-') && + !html.includes('sig') && + !html.includes('.blob.core.windows.net') + ) { + return html + } + const parser = new DOMParser() + const doc = parser.parseFromString(html, 'text/html') + const imgs = doc.querySelectorAll('img[src]') + let changed = false + + imgs.forEach(img => { + const stablePath = img.getAttribute('dt-data-path') + const src = img.getAttribute('src') + let nextSrc = src + + if (stablePath) { + nextSrc = resolvePhotoURL(stablePath) + } else if (isCloudSignedUrl(src)) { + nextSrc = resolvePhotoURL(extractStorageKey(src)) + } + + if (nextSrc && nextSrc !== src) { + img.setAttribute('src', nextSrc) + changed = true + } + }) + + return changed ? doc.body.innerHTML : html +} + +export { isPlusAccount, refreshSignedUrlsInHtml, resolvePhotoURL } diff --git a/src/views/ChoreEdit/ChoreEdit.jsx b/src/views/ChoreEdit/ChoreEdit.jsx index 4d5c78e..7ac6cd4 100644 --- a/src/views/ChoreEdit/ChoreEdit.jsx +++ b/src/views/ChoreEdit/ChoreEdit.jsx @@ -1,4 +1,12 @@ -import { Add, ArrowDropDown, HorizontalRule, Save } from '@mui/icons-material' +import { + Add, + ArrowDropDown, + AttachFile, + Delete, + HorizontalRule, + Save, + UploadFile, +} from '@mui/icons-material' import { Avatar, Box, @@ -43,8 +51,13 @@ import { import { useCircleMembers, useUserProfile } from '../../queries/UserQueries.jsx' import { useNotification } from '../../service/NotificationProvider' import { getTextColorFromBackgroundColor } from '../../utils/Colors.jsx' -import { GetAllCircleMembers, GetThings } from '../../utils/Fetcher' -import { isPlusAccount } from '../../utils/Helpers' +import { + DeleteChoreAttachment, + GetAllCircleMembers, + GetThings, + UploadChoreAttachment, +} from '../../utils/Fetcher' +import { isPlusAccount, resolvePhotoURL } from '../../utils/Helpers' import Priorities from '../../utils/Priorities.jsx' import { getIconComponent } from '../../utils/ProjectIcons' import { getSafeBottomPadding } from '../../utils/SafeAreaUtils.js' @@ -53,6 +66,7 @@ import LoadingComponent from '../components/Loading.jsx' import RichTextEditor from '../components/RichTextEditor.jsx' import SubTasks from '../components/SubTask.jsx' import { useLabels } from '../Labels/LabelQueries' +import AttachmentViewerModal from '../Modals/Inputs/AttachmentViewerModal' import ConfirmationModal from '../Modals/Inputs/ConfirmationModal' import LabelModal from '../Modals/Inputs/LabelModal' import { useProjects } from '../Projects/ProjectQueries' @@ -117,7 +131,13 @@ const ChoreEdit = () => { const [createdBy, setCreatedBy] = useState(0) const [errors, setErrors] = useState({}) const [attemptToSave, setAttemptToSave] = useState(false) + const [draftId] = useState(() => crypto.randomUUID()) + const [attachments, setAttachments] = useState([]) + const [isUploadingAttachment, setIsUploadingAttachment] = useState(false) const [addLabelModalOpen, setAddLabelModalOpen] = useState(false) + const [attachmentViewerConfig, setAttachmentViewerConfig] = useState({ + isOpen: false, + }) const [showSavePrivacyDefault, setShowSavePrivacyDefault] = useState(false) const [privacySaved, setPrivacySaved] = useState(false) const [showSaveNotificationDefault, setShowSaveNotificationDefault] = @@ -362,6 +382,7 @@ const ChoreEdit = () => { deadlineOffset: deadlineOffset < 0 ? null : deadlineOffset, priority: priority, projectId: projectId === 'default' ? null : projectId, + draftId: newChoreId > 0 ? undefined : draftId, } let SaveFunction = createChoreMutation.mutateAsync if (newChoreId > 0) { @@ -576,6 +597,7 @@ const ChoreEdit = () => { setCreatedBy(data.res.createdBy) setUpdatedBy(data.res.updatedBy) + setAttachments(data.res.attachments || []) } }, [choreData, isChoreLoading, searchParams]) @@ -941,6 +963,175 @@ const ChoreEdit = () => { /> + + + Attachments + Files attached to this task + + {attachments.length > 0 && ( + + {attachments.map((att, idx) => ( + { + const url = resolvePhotoURL(att.sign || att.file_path) + const ext = (att.file_name || '') + .split('.') + .pop() + .toLowerCase() + const isImage = [ + 'jpg', + 'jpeg', + 'png', + 'gif', + 'webp', + 'bmp', + 'svg', + ].includes(ext) + if (isImage) { + setAttachmentViewerConfig({ + isOpen: true, + url, + fileName: att.file_name, + onClose: () => + setAttachmentViewerConfig({ isOpen: false }), + }) + } else { + const a = document.createElement('a') + a.href = url + a.download = att.file_name || 'attachment' + document.body.appendChild(a) + a.click() + document.body.removeChild(a) + } + }} + sx={{ + display: 'flex', + alignItems: 'center', + gap: 1, + p: 1, + borderRadius: 'sm', + border: '1px solid', + borderColor: 'neutral.outlinedBorder', + cursor: 'pointer', + '&:hover': { bgcolor: 'neutral.softHoverBg' }, + }} + > + + + {att.file_name} + + {att.size_bytes && ( + + {(att.size_bytes / 1024).toFixed(1)} KB + + )} + {choreId && ( + { + event.stopPropagation() + DeleteChoreAttachment(choreId, att.file_path) + .then(() => { + setAttachments(prev => + prev.filter(a => a.file_path !== att.file_path), + ) + }) + .catch(() => { + showError({ + title: 'Delete Failed', + message: 'Failed to delete attachment.', + }) + }) + }} + > + + + )} + {!choreId && ( + { + event.stopPropagation() + setAttachments(prev => + prev.filter((_, i) => i !== idx), + ) + }} + > + + + )} + + ))} + + )} + + + {/* Section 2: Assignment & Responsibility */} @@ -1732,6 +1923,7 @@ const ChoreEdit = () => { )} + {addLabelModalOpen && ( err instanceof TypeError && err.message === 'Failed to fetch' @@ -124,6 +127,7 @@ const ChoreView = () => { const [chorePriority, setChorePriority] = useState(null) const [noteViewerConfig, setNoteViewerConfig] = useState({ isOpen: false }) const [timerActionConfig, setTimerActionConfig] = useState({ isOpen: false }) + const [attachmentBrowserOpen, setAttachmentBrowserOpen] = useState(false) const { data: circleMembersData, isLoading: isCircleMembersLoading } = useCircleMembers() const { data: userProfile } = useUserProfile() @@ -132,6 +136,7 @@ const ChoreView = () => { const { data: choreData, isLoading: isChoreLoading } = useChoreDetails(choreId) const { data: choreHistoryData } = useChoreHistory(choreId) + const { data: pendingCmds } = usePendingCommands(choreId) const choreHistory = choreHistoryData?.res || [] @@ -651,16 +656,14 @@ const ChoreView = () => { justifyContent: 'center', alignItems: 'center', mb: 1, + flexWrap: 'wrap', + gap: 0.5, }} > {chore?.labelsV2?.map((label, index) => ( { {label?.name} ))} + + {chore?.attachments?.length > 0 && ( + } + size='md' + variant='soft' + color='neutral' + onClick={() => setAttachmentBrowserOpen(true)} + sx={{ cursor: 'pointer' }} + > + {chore.attachments.length}{' '} + {chore.attachments.length === 1 ? 'attachment' : 'attachments'} + + )} @@ -918,7 +935,7 @@ const ChoreView = () => { whiteSpace: 'pre-wrap', wordBreak: 'break-word', }} - dangerouslySetInnerHTML={{ __html: raw }} + dangerouslySetInnerHTML={{ __html: refreshSignedUrlsInHtml(raw) }} /> ) : ( { whiteSpace: 'pre-wrap', wordBreak: 'break-word', }} - dangerouslySetInnerHTML={{ __html: raw }} + dangerouslySetInnerHTML={{ __html: refreshSignedUrlsInHtml(raw) }} /> ) : ( { + setAttachmentBrowserOpen(false)} + /> ) diff --git a/src/views/Modals/Inputs/AttachmentBrowserModal.jsx b/src/views/Modals/Inputs/AttachmentBrowserModal.jsx new file mode 100644 index 0000000..3de68f5 --- /dev/null +++ b/src/views/Modals/Inputs/AttachmentBrowserModal.jsx @@ -0,0 +1,138 @@ +import { AttachFile, Close, Image } from '@mui/icons-material' +import { Box, Button, CircularProgress, List, ListItem, ListItemButton, Typography } from '@mui/joy' +import { useEffect, useState } from 'react' +import { useResponsiveModal } from '../../../hooks/useResponsiveModal' +import { GetChoreAttachments } from '../../../utils/Fetcher' +import { resolvePhotoURL } from '../../../utils/Helpers' +import AttachmentViewerModal from './AttachmentViewerModal' + +const IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg'] + +const isImageFile = fileName => { + if (!fileName) return false + const ext = fileName.split('.').pop().toLowerCase() + return IMAGE_EXTENSIONS.includes(ext) +} + +const downloadFile = (url, fileName) => { + const a = document.createElement('a') + a.href = url + a.download = fileName || 'attachment' + a.rel = 'noopener' + document.body.appendChild(a) + a.click() + document.body.removeChild(a) +} + +function AttachmentBrowserModal({ choreId, isOpen, onClose }) { + const { ResponsiveModal } = useResponsiveModal() + const [attachments, setAttachments] = useState([]) + const [isLoading, setIsLoading] = useState(false) + const [viewerConfig, setViewerConfig] = useState({ isOpen: false }) + + useEffect(() => { + if (!isOpen || !choreId) return + setIsLoading(true) + GetChoreAttachments(choreId) + .then(async res => { + if (!res.ok) throw new Error('Failed to fetch attachments') + return res.json() + }) + .then(data => setAttachments(Array.isArray(data) ? data : [])) + .catch(() => setAttachments([])) + .finally(() => setIsLoading(false)) + }, [isOpen, choreId]) + + const handleClose = () => { + setAttachments([]) + onClose?.() + } + + const handleAttachmentClick = attachment => { + const url = resolvePhotoURL(attachment.sign) + if (isImageFile(attachment.file_name)) { + setViewerConfig({ + isOpen: true, + url, + fileName: attachment.file_name, + onClose: () => setViewerConfig({ isOpen: false }), + }) + } else { + downloadFile(url, attachment.file_name) + } + } + + return ( + <> + + + + } + > + {isLoading ? ( + + + + ) : attachments.length === 0 ? ( + + No attachments found. + + ) : ( + + {attachments.map((attachment, index) => ( + + handleAttachmentClick(attachment)} + sx={{ borderRadius: 'sm', gap: 1.5, py: 1 }} + > + {isImageFile(attachment.file_name) ? ( + + ) : ( + + )} + + + {attachment.file_name || `File ${index + 1}`} + + {attachment.size_bytes > 0 && ( + + {(attachment.size_bytes / 1024).toFixed(1)} KB + + )} + + + + ))} + + )} + + + + ) +} + +export default AttachmentBrowserModal diff --git a/src/views/Modals/Inputs/AttachmentViewerModal.jsx b/src/views/Modals/Inputs/AttachmentViewerModal.jsx new file mode 100644 index 0000000..317b2f9 --- /dev/null +++ b/src/views/Modals/Inputs/AttachmentViewerModal.jsx @@ -0,0 +1,116 @@ +import { Browser } from '@capacitor/browser' +import { Capacitor } from '@capacitor/core' +import { Close, Download } from '@mui/icons-material' +import { Box, Button, CircularProgress, Typography } from '@mui/joy' +import { useState } from 'react' +import { useResponsiveModal } from '../../../hooks/useResponsiveModal' + +const openUrl = async url => { + if (Capacitor.isNativePlatform()) { + await Browser.open({ url }) + } else { + window.open(url, '_blank', 'noopener,noreferrer') + } +} + +const downloadUrl = (url, fileName) => { + if (Capacitor.isNativePlatform()) { + Browser.open({ url }) + } else { + const a = document.createElement('a') + a.href = url + a.download = fileName || 'attachment' + a.rel = 'noopener' + document.body.appendChild(a) + a.click() + document.body.removeChild(a) + } +} + +function AttachmentViewerModal({ config }) { + const { ResponsiveModal } = useResponsiveModal() + const [imgLoaded, setImgLoaded] = useState(false) + const [imgError, setImgError] = useState(false) + + const { isOpen, url, fileName, onClose } = config || {} + + const handleClose = () => { + setImgLoaded(false) + setImgError(false) + onClose?.() + } + + return ( + + + + + } + > + + {!imgLoaded && !imgError && ( + + )} + {imgError ? ( + + Failed to load image. + + ) : ( + url && openUrl(url)} + onLoad={() => setImgLoaded(true)} + onError={() => { + setImgLoaded(true) + setImgError(true) + }} + sx={{ + cursor: url ? 'zoom-in' : 'default', + maxWidth: '100%', + maxHeight: '65vh', + borderRadius: 'md', + objectFit: 'contain', + display: imgLoaded && !imgError ? 'block' : 'none', + }} + /> + )} + + + ) +} + +export default AttachmentViewerModal diff --git a/src/views/Settings/ProfileSettings.jsx b/src/views/Settings/ProfileSettings.jsx index 10ed059..27e2f28 100644 --- a/src/views/Settings/ProfileSettings.jsx +++ b/src/views/Settings/ProfileSettings.jsx @@ -25,13 +25,12 @@ import SettingsLayout from './SettingsLayout' const ProfileSettings = () => { const { t } = useTranslation('settings') const queryClient = useQueryClient() - const { data: userProfile } = useUserProfile() + const { data: userProfile, refetch: refetchUserProfile } = useUserProfile() const { showSuccess, showError } = useNotification() const [displayName, setDisplayName] = useState(userProfile?.displayName || '') const [timezone, setTimezone] = useState( userProfile?.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone, ) - const [photoURL, setPhotoURL] = useState(userProfile?.image || '') const [isUploading, setIsUploading] = useState(false) const [isSaving, setIsSaving] = useState(false) const fileInputRef = useRef() @@ -89,10 +88,9 @@ const ProfileSettings = () => { formData.append('file', compressedFile, 'profile.jpg') const response = await apiClient.upload('/users/profile_photo', formData) if (!response.ok) throw new Error('Upload failed') - const data = await response.json() - const url = resolvePhotoURL(data.url || data.sign) + await response.json() - setPhotoURL(url) + refetchUserProfile() // Refresh user profile to get the new photoURL showSuccess({ title: t('profile.photoUpdated'), message: t('profile.photoUpdatedMessage'), @@ -155,7 +153,7 @@ const ProfileSettings = () => { maxWidth: 400, }} > - + - )} - - {!hasSubTasks && ( - - )} - - - {hasDescription && ( - - Description: -
- setShowScan(true)} + onPhotoSelected={dataUrl => { + setPendingPhotoUrl(dataUrl) + setShowScan(true) + }} + placeholder='Type your task...' + onChange={text => { + setTaskText(text) + if (!text) setTaskTitle('') + }} + customRenderer={renderedParts} + onEnterPressed={handleEnterPressed} + suggestions={{ + '#': { + value: 'id', + display: 'name', + options: userLabels ? userLabels : [], + }, + '!': { + value: 'id', + display: 'name', + options: [ + { id: '1', name: 'P1' }, + { id: '2', name: 'P2' }, + { id: '3', name: 'P3' }, + { id: '4', name: 'P4' }, + ], + }, + '@': { + value: 'userId', + display: 'displayName', + options: [ + { userId: 'anyone', displayName: 'Anyone' }, + ...(circleMembers?.res || []), + ], + }, + '*': { + value: 'id', + display: 'name', + options: [ + { id: '1', name: '1 point' }, + { id: '5', name: '5 points' }, + { id: '10', name: '10 points' }, + { id: '25', name: '25 points' }, + { id: '50', name: '50 points' }, + { id: '100', name: '100 points' }, + ], + }, + }} /> -
-
+ + + + { + setDueDate(null) + setDueDateOnly(null) + setDueTime(null) + setUseCustomTime(false) + }} + /> + setFrequency(null)} + /> + setPriority(0)} + emptyDisplay={pickerEmptyDisplay} + priorityColors={priorityColors} + priorityLabels={priorityLabels} + /> + { + if (!userId) { + setAssignees([]) + } else { + setAssignees([{ userId }]) + } + }} + onClear={() => setAssignees([])} + currentUserId={userProfile?.id} + members={circleMembers?.res || []} + /> + setLabelsV2([])} + labels={userLabels || []} + /> + setAttachments([])} + emptyDisplay={pickerEmptyDisplay} + entityType='chore_attachment_draft' + draftId={draftId} + /> + setNotificationMetadata({ templates: [] })} + emptyDisplay={pickerEmptyDisplay} + /> + + + + {!hasDescription && ( + + )} + {!hasSubTasks && ( + + )} + + + {hasDescription && ( + + Description: +
+ +
+
+ )} + {hasSubTasks && ( + + Subtasks: + + + )} + )} - {hasSubTasks && ( - - Subtasks: - - + + {showScan && ( + { + setShowScan(false) + setPendingPhotoUrl(null) + }} + /> )} + ) } diff --git a/src/views/components/AttachmentPickerField.jsx b/src/views/components/AttachmentPickerField.jsx index 6f9c385..fc626a8 100644 --- a/src/views/components/AttachmentPickerField.jsx +++ b/src/views/components/AttachmentPickerField.jsx @@ -19,11 +19,12 @@ const AttachmentPickerField = ({ emptyDisplay = 'icon-text', entityType = 'chore_attachment', entityId, + draftId, }) => { const [isOpen, setIsOpen] = useState(false) const [isUploading, setIsUploading] = useState(false) const buttonRef = useRef(null) - const { uploadFile } = useFileUpload({ entityType, entityId }) + const { uploadFile } = useFileUpload({ entityType, entityId, draftId }) useEffect(() => { if (!isOpen) return diff --git a/src/views/components/PhotoTaskModal.jsx b/src/views/components/PhotoTaskModal.jsx new file mode 100644 index 0000000..100fbf6 --- /dev/null +++ b/src/views/components/PhotoTaskModal.jsx @@ -0,0 +1,595 @@ +import { + ArrowBack, + CameraAlt, + CheckCircle, + Close, + DocumentScanner, + PhotoCamera, + Replay, + TextSnippet, +} from '@mui/icons-material' +import { + Box, + Button, + CircularProgress, + IconButton, + LinearProgress, + Typography, +} from '@mui/joy' +import { useCallback, useEffect, useRef, useState } from 'react' +import { useDocumentScanner } from '../../hooks/useDocumentScanner' +import { useResponsiveModal } from '../../hooks/useResponsiveModal' +import { localAIService } from '../../service/LocalAIService' + +const SYSTEM_PROMPT = `You are helping create tasks for a household task management app. + +Given OCR text extracted from a photo, identify the most useful task a person should add to their task list. + +The task title should always start with an action verb when possible. + +Examples: + +Bill -> "Pay water bill" +Appointment -> "Attend eye doctor appointment" +Invitation -> "RSVP for wedding" +Renewal Notice -> "Renew vehicle registration" +Package Notice -> "Pick up package" +School Form -> "Complete school permission form" + +Action Priority Rules: + +1. Payments and bills +2. Deadlines and renewals +3. Appointments +4. Required forms +5. Informational actions (view, read, review) + + +Rules: + +Generate at most one task. +Focus on the most important action. +Extract due dates and deadlines. +Use appointment dates as due dates when appropriate. +Do not invent information. +If the content contains no actionable item, return null values. +Include any important ID or URL or instructions in the description +Titles must be specific and useful at a glance. +Include the organization, provider, event, or subject when available. +Avoid generic document names. +Return valid JSON only. +Output: + +{ + "taskName": string | null, + "description": string | null, + "dueDate": string | null, + "confidence": number +}` + +async function runNativeOCR(imageSource) { + const { Ocr } = await import('@jcesarmobile/capacitor-ocr') + + // Convert Capacitor WebView file URL → native file:// URL the plugin can read + const image = imageSource.includes('/_capacitor_file_/') + ? 'file://' + imageSource.replace(/^https?:\/\/localhost\/_capacitor_file_/, '') + : imageSource + + const result = await Ocr.process({ image }) + return result.results.map(r => r.text).join('\n').trim() +} + +async function runOCR(imageSource, onProgress) { + const { createWorker } = await import('tesseract.js') + const worker = await createWorker('eng', 1, { + logger: m => { + if (m.status === 'recognizing text' && onProgress) { + onProgress(Math.round(m.progress * 100)) + } + }, + }) + const { data } = await worker.recognize(imageSource) + await worker.terminate() + return data.text?.trim() || '' +} + +async function extractTaskFromOCR(ocrText) { + const messages = [ + { role: 'system', content: SYSTEM_PROMPT }, + { role: 'user', content: `OCR Text:\n${ocrText}` }, + ] + + const result = await localAIService.plainChat(messages) + if (!result) return null + + const jsonMatch = result.match(/\{[\s\S]*\}/) + if (!jsonMatch) return null + + try { + return JSON.parse(jsonMatch[0]) + } catch { + return null + } +} + +const PhotoTaskModal = ({ open, onClose, onTaskExtracted }) => { + const { ResponsiveModal } = useResponsiveModal() + const { isNativeScanner, scanDocument } = useDocumentScanner() + const videoRef = useRef(null) + const canvasRef = useRef(null) + const streamRef = useRef(null) + const fileInputRef = useRef(null) + + const [phase, setPhase] = useState('capture') // capture | preview | ocr | llm | done | error + const [capturedImage, setCapturedImage] = useState(null) + const [ocrProgress, setOcrProgress] = useState(0) + const [ocrText, setOcrText] = useState('') + const [ocrMethod, setOcrMethod] = useState('tesseract') + const [showRawText, setShowRawText] = useState(false) + const [taskResult, setTaskResult] = useState(null) + const [errorMsg, setErrorMsg] = useState('') + const [cameraAvailable, setCameraAvailable] = useState(true) + + const startCamera = useCallback(async () => { + try { + const stream = await navigator.mediaDevices.getUserMedia({ + video: { facingMode: 'environment' }, + }) + streamRef.current = stream + if (videoRef.current) { + videoRef.current.srcObject = stream + } + setCameraAvailable(true) + } catch { + setCameraAvailable(false) + } + }, []) + + const stopCamera = useCallback(() => { + if (streamRef.current) { + streamRef.current.getTracks().forEach(t => t.stop()) + streamRef.current = null + } + }, []) + + useEffect(() => { + if (open && phase === 'capture' && !isNativeScanner) { + startCamera() + } + return () => { + stopCamera() + } + }, [open, phase, isNativeScanner, startCamera, stopCamera]) + + const handleCapture = () => { + if (!videoRef.current || !canvasRef.current) return + const video = videoRef.current + const canvas = canvasRef.current + canvas.width = video.videoWidth + canvas.height = video.videoHeight + canvas.getContext('2d').drawImage(video, 0, 0) + const dataUrl = canvas.toDataURL('image/jpeg', 0.9) + setCapturedImage(dataUrl) + stopCamera() + setPhase('preview') + } + + const handleFileSelect = e => { + const file = e.target.files?.[0] + if (!file) return + const reader = new FileReader() + reader.onload = ev => { + setCapturedImage(ev.target.result) + stopCamera() + setPhase('preview') + } + reader.readAsDataURL(file) + } + + const handleBackToPreview = () => { + setOcrText('') + setTaskResult(null) + setErrorMsg('') + setOcrProgress(0) + setShowRawText(false) + setPhase('preview') + } + + const handleProcess = async (method = 'tesseract') => { + setOcrMethod(method) + setPhase('ocr') + setOcrProgress(0) + setErrorMsg('') + setShowRawText(false) + + try { + let text + if (method === 'native') { + try { + text = await runNativeOCR(capturedImage) + } catch { + throw new Error('Native OCR is only available on iOS and Android devices.') + } + } else { + text = await runOCR(capturedImage, pct => setOcrProgress(pct)) + } + setOcrText(text) + + if (!text) { + setErrorMsg('No text found in the image. Please try a clearer photo.') + setPhase('error') + return + } + + setPhase('llm') + const task = await extractTaskFromOCR(text) + + if (!task || !task.taskName) { + setErrorMsg('Could not identify a task from this image. Please try a different photo.') + setPhase('error') + return + } + + setTaskResult(task) + setPhase('done') + } catch (e) { + setErrorMsg(`Processing failed: ${e.message || 'Unknown error'}`) + setPhase('error') + } + } + + const handleRetake = () => { + setCapturedImage(null) + setOcrText('') + setTaskResult(null) + setErrorMsg('') + setOcrProgress(0) + setShowRawText(false) + setPhase('capture') + } + + const handleNativeScan = async () => { + const { image, cancelled, error } = await scanDocument() + if (cancelled) return + if (error || !image) { + setErrorMsg(error ? `Scanner error: ${error}` : 'Scan cancelled or failed.') + setPhase('error') + return + } + setCapturedImage(image) + stopCamera() + setPhase('preview') + } + + const handleConfirm = () => { + if (taskResult) { + onTaskExtracted(taskResult) + } + handleClose() + } + + const handleClose = () => { + stopCamera() + setCapturedImage(null) + setOcrText('') + setTaskResult(null) + setErrorMsg('') + setOcrProgress(0) + setShowRawText(false) + setPhase('capture') + onClose() + } + + const isProcessing = phase === 'ocr' || phase === 'llm' + + return ( + + + {(phase === 'capture' || phase === 'preview') && ( + + {phase === 'capture' && !isNativeScanner && cameraAvailable && ( + + )} + + + + {isProcessing && ( + + {capturedImage && ( + Processing + )} + + {phase === 'ocr' && ocrMethod === 'tesseract' && ( + <> + + Reading text from image… {ocrProgress}% + + + + )} + {phase === 'ocr' && ocrMethod === 'native' && ( + Running native OCR… + )} + {phase === 'llm' && ( + Identifying task with AI… + )} + + )} + + {phase === 'done' && taskResult && ( + + + + Task identified + + + {taskResult.taskName} + {taskResult.description && ( + + {taskResult.description} + + )} + {taskResult.dueDate && ( + + Due: {taskResult.dueDate} + + )} + + {ocrText && ( + <> + + {showRawText && ( + + + {ocrText} + + + )} + + )} + + )} + + {phase === 'error' && ( + + {errorMsg} + + )} + + + {phase === 'capture' && ( + <> + + + {isNativeScanner ? ( + + ) : ( + cameraAvailable && ( + + ) + )} + + )} + + {phase === 'preview' && ( + <> + + {isNativeScanner && ( + + )} + + + )} + + {phase === 'error' && ( + <> + + + + )} + + {phase === 'done' && ( + <> + + + + + )} + + {!isProcessing && ( + + + + )} + + + + ) +} + +export default PhotoTaskModal diff --git a/src/views/components/RichTextEditor.css b/src/views/components/RichTextEditor.css index 20147ea..042bf74 100644 --- a/src/views/components/RichTextEditor.css +++ b/src/views/components/RichTextEditor.css @@ -53,20 +53,33 @@ } /* Material-UI style customizations for Quill toolbar */ +.quill-root { + border-radius: 10px; + overflow: hidden; + background-color: var(--joy-palette-neutral-softBg, #f0f4f8); + transition: box-shadow 0.15s ease, background-color 0.15s ease; +} + +.quill-root:focus-within { + box-shadow: 0 0 0 1px var(--joy-palette-primary-outlinedBorder, rgba(11, 107, 203, 0.15)); +} + +.quill-root:hover:not(:focus-within) { + background-color: var(--joy-palette-neutral-softHoverBg, #dde7ee); +} + .quill-root .ql-toolbar.ql-snow { - border: 1px solid var(--joy-palette-neutral-outlinedBorder, #dde7ee); - border-bottom: none; - border-radius: 8px 8px 0 0; - background: var(--joy-palette-background-surface, #fff); - box-shadow: var(--joy-shadow-xs, 0px 1px 2px 0px rgba(16, 24, 40, 0.05)); - padding: 12px 16px; + border: none; + border-bottom: 1px solid var(--joy-palette-neutral-softActiveBg, rgba(99, 107, 116, 0.16)); + border-radius: 0; + background: transparent; + box-shadow: none; + padding: 8px 12px; } .quill-root .ql-container.ql-snow { - border: 1px solid var(--joy-palette-neutral-outlinedBorder, #dde7ee); - border-top: none; - border-radius: 0 0 8px 8px; - background: var(--joy-palette-background-surface, #fff); + border: none; + background: transparent; } /* Style toolbar buttons with Material-UI look */ diff --git a/src/views/components/RichTextEditor.jsx b/src/views/components/RichTextEditor.jsx index adfb070..fe875eb 100644 --- a/src/views/components/RichTextEditor.jsx +++ b/src/views/components/RichTextEditor.jsx @@ -2,6 +2,33 @@ import imageCompression from 'browser-image-compression' import Quill from 'quill' import 'quill/dist/quill.snow.css' import QuillMarkdown from 'quilljs-markdown' + +// Extend the built-in Image blot to preserve dt-data-path +const ImageBlot = Quill.import('formats/image') +class DtImageBlot extends ImageBlot { + static create(value) { + const node = super.create(typeof value === 'string' ? value : value.src) + if (value?.path) node.setAttribute('dt-data-path', value.path) + return node + } + static value(node) { + return { src: node.getAttribute('src'), path: node.getAttribute('dt-data-path') } + } + static formats(node) { + return { 'dt-data-path': node.getAttribute('dt-data-path') } + } + format(name, value) { + if (name === 'dt-data-path') { + if (value) this.domNode.setAttribute('dt-data-path', value) + else this.domNode.removeAttribute('dt-data-path') + } else { + super.format(name, value) + } + } +} +DtImageBlot.blotName = 'image' +DtImageBlot.tagName = 'img' +Quill.register(DtImageBlot, true) import { forwardRef, useCallback, @@ -12,7 +39,11 @@ import { import { useUserProfile } from '../../queries/UserQueries' import { useNotification } from '../../service/NotificationProvider' import { apiClient } from '../../utils/ApiClient' -import { isPlusAccount, resolvePhotoURL } from '../../utils/Helpers' +import { + isPlusAccount, + refreshSignedUrlsInHtml, + resolvePhotoURL, +} from '../../utils/Helpers' import './RichTextEditor.css' const RichTextEditor = forwardRef( @@ -32,6 +63,7 @@ const RichTextEditor = forwardRef( const { data: userProfile } = useUserProfile() const quillRef = useRef(null) const editorRef = useRef(null) + const initialContentSet = useRef(false) // Expose focus method to parent components useImperativeHandle( @@ -141,11 +173,16 @@ const RichTextEditor = forwardRef( return } const data = await response.json() - const url = resolvePhotoURL(data.url || data.sign) - // Insert image into Quill + // Prefer the backend-proxied path (data.sign) over the direct cloud + // signed URL (data.url) — the proxy re-signs on every request so the + // embedded src never expires. + const path = data.path + const url = resolvePhotoURL(data.sign || data.url) + // Insert image into Quill with dt-data-path tracked by the custom blot const quill = editorRef.current const range = quill.getSelection() - quill.insertEmbed(range ? range.index : 0, 'image', url) + const insertIndex = range ? range.index : 0 + quill.insertEmbed(insertIndex, 'image', { src: url, path }) } catch (error) { console.error('Error during image processing or upload:', error) showError({ @@ -202,7 +239,11 @@ const RichTextEditor = forwardRef( useEffect(() => { if (editorRef.current && isEditable) { if (editorRef.current.root.innerHTML !== value) { - editorRef.current.root.innerHTML = value || '' + const html = !initialContentSet.current + ? refreshSignedUrlsInHtml(value || '') + : value || '' + initialContentSet.current = true + editorRef.current.root.innerHTML = html } } }, [value, isEditable]) @@ -227,7 +268,7 @@ const RichTextEditor = forwardRef( boxShadow: 'var(--joy-shadow-xs, 0px 1px 2px 0px rgba(16, 24, 40, 0.05))', }} - dangerouslySetInnerHTML={{ __html: value }} + dangerouslySetInnerHTML={{ __html: refreshSignedUrlsInHtml(value) }} /> ) } diff --git a/src/views/components/ScanToTask/ScanPanel.jsx b/src/views/components/ScanToTask/ScanPanel.jsx new file mode 100644 index 0000000..855ac07 --- /dev/null +++ b/src/views/components/ScanToTask/ScanPanel.jsx @@ -0,0 +1,309 @@ +import { + CameraAlt, + DocumentScanner, + PhotoCamera, + Replay, + WarningAmber, +} from '@mui/icons-material' +import { + Box, + Button, + CircularProgress, + LinearProgress, + Typography, +} from '@mui/joy' +import { useEffect } from 'react' +import { useScanToTask } from './useScanToTask' + +/** + * Inline scan-to-task panel. Mounts inside AddTaskModal — no second modal. + * + * Flow: capture → (auto) processing → done [calls onTaskExtracted + onClose] + * → error [retake or cancel] + */ +const ScanPanel = ({ open, onTaskExtracted, onClose, initialImageUrl }) => { + const { + isNativeScanner, + phase, + capturedImage, + ocrProgress, + taskResult, + errorMsg, + cameraAvailable, + videoRef, + canvasRef, + fileInputRef, + startCamera, + stopCamera, + capture, + handleFileSelect, + handleNativeScan, + retake, + activate, + reset, + } = useScanToTask() + + // Start/stop based on open state + useEffect(() => { + if (open) { + activate(initialImageUrl) + } else { + reset() + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [open, initialImageUrl]) + + // Start camera when entering capture phase on web + useEffect(() => { + if (phase === 'capture' && !isNativeScanner) { + startCamera() + } + if (phase !== 'capture') { + stopCamera() + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [phase, isNativeScanner]) + + // Auto-close and populate when done + useEffect(() => { + if (phase === 'done' && taskResult) { + onTaskExtracted(taskResult) + onClose() + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [phase, taskResult]) + + if (!open) return null + + const isProcessing = phase === 'processing' + + return ( + + {/* ── Capture phase ── */} + {phase === 'capture' && ( + <> + + {isNativeScanner && ( + + + + Tap "Scan Document" to open the scanner + + + )} + + {!isNativeScanner && cameraAvailable && ( + + + + + + + + + {isNativeScanner ? ( + + ) : ( + cameraAvailable && ( + + ) + )} + + + + )} + + {/* ── Processing phase ── */} + {isProcessing && ( + + {capturedImage && ( + + Processing + + + + + )} + + + {ocrProgress > 0 && ocrProgress < 100 + ? `Reading text… ${ocrProgress}%` + : ocrProgress >= 100 + ? 'Identifying task with AI…' + : 'Starting…'} + + + {ocrProgress > 0 && ocrProgress < 100 && ( + + )} + + )} + + {/* ── Error phase ── */} + {phase === 'error' && ( + + {capturedImage && ( + Failed scan + )} + + + {errorMsg} + + + + + + + )} + + + + ) +} + +export default ScanPanel diff --git a/src/views/components/ScanToTask/useScanToTask.js b/src/views/components/ScanToTask/useScanToTask.js new file mode 100644 index 0000000..66b5d95 --- /dev/null +++ b/src/views/components/ScanToTask/useScanToTask.js @@ -0,0 +1,256 @@ +import { useCallback, useRef, useState } from 'react' +import { useDocumentScanner } from '../../../hooks/useDocumentScanner' +import { localAIService } from '../../../service/LocalAIService' + +const SYSTEM_PROMPT = `You are helping create tasks for a household task management app. + +Given OCR text extracted from a photo, identify the most useful task a person should add to their task list. + +The task title should always start with an action verb when possible. + +Examples: + +Bill -> "Pay water bill" +Appointment -> "Attend eye doctor appointment" +Invitation -> "RSVP for wedding" +Renewal Notice -> "Renew vehicle registration" +Package Notice -> "Pick up package" +School Form -> "Complete school permission form" + +Action Priority Rules: + +1. Payments and bills +2. Deadlines and renewals +3. Appointments +4. Required forms +5. Informational actions (view, read, review) + +Rules: + +Generate at most one task. +Focus on the most important action. +Extract due dates and deadlines. +Use appointment dates as due dates when appropriate. +Do not invent information. +If the content contains no actionable item, return null values. +Include any important ID or URL or instructions in the description. +Titles must be specific and useful at a glance. +Include the organization, provider, event, or subject when available. +Avoid generic document names. +Return valid JSON only. + +Output: + +{ + "taskName": string | null, + "description": string | null, + "dueDate": string | null, + "confidence": number +}` + +async function runNativeOCR(imageSource) { + const { Ocr } = await import('@jcesarmobile/capacitor-ocr') + const image = imageSource.includes('/_capacitor_file_/') + ? 'file://' + imageSource.replace(/^https?:\/\/localhost\/_capacitor_file_/, '') + : imageSource + const result = await Ocr.process({ image }) + return result.results.map(r => r.text).join('\n').trim() +} + +async function runBrowserOCR(imageSource, onProgress) { + const { createWorker } = await import('tesseract.js') + const worker = await createWorker('eng', 1, { + logger: m => { + if (m.status === 'recognizing text' && onProgress) { + onProgress(Math.round(m.progress * 100)) + } + }, + }) + const { data } = await worker.recognize(imageSource) + await worker.terminate() + return data.text?.trim() || '' +} + +async function extractTaskFromOCR(ocrText) { + const messages = [ + { role: 'system', content: SYSTEM_PROMPT }, + { role: 'user', content: `OCR Text:\n${ocrText}` }, + ] + const result = await localAIService.plainChat(messages) + if (!result) return null + const jsonMatch = result.match(/\{[\s\S]*\}/) + if (!jsonMatch) return null + try { + return JSON.parse(jsonMatch[0]) + } catch { + return null + } +} + +// phases: idle | capture | processing | done | error +export function useScanToTask() { + const { isNativeScanner, scanDocument } = useDocumentScanner() + const videoRef = useRef(null) + const canvasRef = useRef(null) + const streamRef = useRef(null) + const fileInputRef = useRef(null) + + const [phase, setPhase] = useState('idle') + const [capturedImage, setCapturedImage] = useState(null) + const [ocrProgress, setOcrProgress] = useState(0) + const [taskResult, setTaskResult] = useState(null) + const [errorMsg, setErrorMsg] = useState('') + const [cameraAvailable, setCameraAvailable] = useState(true) + + const startCamera = useCallback(async () => { + try { + const stream = await navigator.mediaDevices.getUserMedia({ + video: { facingMode: 'environment' }, + }) + streamRef.current = stream + if (videoRef.current) { + videoRef.current.srcObject = stream + } + setCameraAvailable(true) + } catch { + setCameraAvailable(false) + } + }, []) + + const stopCamera = useCallback(() => { + if (streamRef.current) { + streamRef.current.getTracks().forEach(t => t.stop()) + streamRef.current = null + } + }, []) + + const processImage = useCallback(async (imageData, method = 'browser') => { + setPhase('processing') + setOcrProgress(0) + setErrorMsg('') + + try { + let text + if (method === 'native') { + text = await runNativeOCR(imageData) + } else { + text = await runBrowserOCR(imageData, pct => setOcrProgress(pct)) + } + + if (!text) { + setErrorMsg('No text found in image. Try a clearer photo.') + setPhase('error') + return + } + + const task = await extractTaskFromOCR(text) + if (!task || !task.taskName) { + setErrorMsg('Could not identify a task. Try a different photo.') + setPhase('error') + return + } + + setTaskResult(task) + setPhase('done') + } catch (e) { + setErrorMsg(e.message || 'Processing failed.') + setPhase('error') + } + }, []) + + const capture = useCallback(() => { + if (!videoRef.current || !canvasRef.current) return + const video = videoRef.current + const canvas = canvasRef.current + canvas.width = video.videoWidth + canvas.height = video.videoHeight + canvas.getContext('2d').drawImage(video, 0, 0) + const dataUrl = canvas.toDataURL('image/jpeg', 0.9) + setCapturedImage(dataUrl) + stopCamera() + processImage(dataUrl, 'browser') + }, [stopCamera, processImage]) + + const handleFileSelect = useCallback( + e => { + const file = e.target.files?.[0] + if (!file) return + const reader = new FileReader() + reader.onload = ev => { + const dataUrl = ev.target.result + setCapturedImage(dataUrl) + stopCamera() + processImage(dataUrl, 'browser') + } + reader.readAsDataURL(file) + }, + [stopCamera, processImage], + ) + + const handleNativeScan = useCallback(async () => { + const { image, cancelled, error } = await scanDocument() + if (cancelled) return + if (error || !image) { + setErrorMsg(error ? `Scanner error: ${error}` : 'Scan failed.') + setPhase('error') + return + } + setCapturedImage(image) + processImage(image, 'native') + }, [scanDocument, processImage]) + + const retake = useCallback(() => { + setCapturedImage(null) + setTaskResult(null) + setErrorMsg('') + setOcrProgress(0) + setPhase('capture') + }, []) + + const activate = useCallback( + (initialImageUrl = null) => { + setCapturedImage(initialImageUrl) + setTaskResult(null) + setErrorMsg('') + setOcrProgress(0) + + if (initialImageUrl) { + processImage(initialImageUrl, 'browser') + return + } + + setPhase('capture') + }, + [processImage], + ) + + const reset = useCallback(() => { + stopCamera() + setCapturedImage(null) + setTaskResult(null) + setErrorMsg('') + setOcrProgress(0) + setPhase('idle') + }, [stopCamera]) + + return { + isNativeScanner, + phase, + capturedImage, + ocrProgress, + taskResult, + errorMsg, + cameraAvailable, + videoRef, + canvasRef, + fileInputRef, + startCamera, + stopCamera, + capture, + handleFileSelect, + handleNativeScan, + retake, + activate, + reset, + } +} diff --git a/src/views/components/SmartTaskTitleInput.css b/src/views/components/SmartTaskTitleInput.css index c2bb002..af7a6f2 100644 --- a/src/views/components/SmartTaskTitleInput.css +++ b/src/views/components/SmartTaskTitleInput.css @@ -31,7 +31,6 @@ font-size: 1.2em; line-height: 1.2em; font-family: inherit; - caret-color: var(--highlight-date-color); } .highlight-date { @@ -61,8 +60,18 @@ .task-input { position: relative; width: 100%; - border-radius: 8px; + border-radius: 10px; box-sizing: border-box; - border: 1px solid var(--joy-palette-neutral-outlinedBorder, #d0d5dd); + border: none; + background-color: var(--joy-palette-neutral-softBg, #f0f4f8); overflow: auto; + transition: box-shadow 0.15s ease, background-color 0.15s ease; +} + +.task-input:focus-within { + box-shadow: 0 0 0 2px var(--joy-palette-primary-outlinedBorder, rgba(11, 107, 203, 0.15)); +} + +.task-input:hover:not(:focus-within) { + background-color: var(--joy-palette-neutral-softHoverBg, #dde7ee); } diff --git a/src/views/components/SmartTaskTitleInput.jsx b/src/views/components/SmartTaskTitleInput.jsx index afc873e..9a66b32 100644 --- a/src/views/components/SmartTaskTitleInput.jsx +++ b/src/views/components/SmartTaskTitleInput.jsx @@ -1,4 +1,5 @@ -import { useColorScheme } from '@mui/joy' +import { CameraEnhance, PhotoFilter } from '@mui/icons-material' +import { IconButton, Tooltip, useColorScheme } from '@mui/joy' import { useEffect, useRef, useState } from 'react' import AutocompleteDropdown from '../TestView/AutocompleteDropdown' import './SmartTaskTitleInput.css' @@ -52,9 +53,13 @@ const SmartTaskTitleInput = ({ suggestions, onEnterPressed, customRenderer, + isNativeScanner, + onScanClick, + onPhotoSelected, }) => { const { mode, setMode } = useColorScheme() const titleInputRef = useRef(null) + const photoInputRef = useRef(null) const [cursorPosition, setCursorPosition] = useState(value?.length) const dropdownRef = useRef(null) const [lastWord, setLastWord] = useState('') @@ -181,9 +186,26 @@ const SmartTaskTitleInput = ({ } } + const handlePhotoInputChange = e => { + const file = e.target.files?.[0] + if (!file || !onPhotoSelected) return + const reader = new FileReader() + reader.onload = ev => onPhotoSelected(ev.target.result) + reader.readAsDataURL(file) + e.target.value = '' + } + + const showNativeButtons = isNativeScanner && !value + const MIC_BUTTON_WIDTH = + showNativeButtons && onPhotoSelected && onScanClick + ? '5rem' + : showNativeButtons + ? '2.5rem' + : '0rem' + return (
-
+