diff --git a/locales/en/en.json b/locales/en/en.json index 034c24e2..47868a98 100644 --- a/locales/en/en.json +++ b/locales/en/en.json @@ -403,6 +403,8 @@ "select": "Select", "syncMode": "Sync mode", "title": "Create sync", + "paused": "Paused", + "excludeDotFiles": "Exclude dot files (recommended)", "mode": { "twoWay": "Two way", "localToCloud": "Local to cloud", @@ -539,7 +541,7 @@ "transfers": { "title": "Transfers", "remaining": "About {{time}} remaining", - "noActiveTransfers": "No active transfers", + "noActiveTransfers": "No transfers yet", "state": { "finished": "Finished", "queued": "Queued", @@ -766,6 +768,14 @@ "name": "Logout", "info": "Logout", "action": "Logout" + }, + "closeToTray": { + "name": "Close to tray", + "info": "Hide Filen in the tray instead of minimizing the window" + }, + "autoLaunch": { + "name": "Autostart", + "info": "Start Filen automatically on system start" } } }, @@ -901,7 +911,7 @@ }, "personalInformation": { "name": "Personal information", - "info": "Edit your personal information. Will be used for invoicing and is not public", + "info": "Edit your personal information. Only for business customers. Will be used for invoicing and is not public.", "action": "Edit" }, "fileVersioning": { @@ -1133,28 +1143,28 @@ }, "enabled": { "name": "Enabled", - "info": "Enable or disable the virtual drive. Refresh your explorer when toggling this option." + "info": "Enable or disable the virtual drive. Refresh your explorer when toggling this option" }, "mountPoint": { "name": "Mount point", - "info": "Set the mount point to a local directory. The local directory needs to be empty and readale/writable.", + "info": "Set the mount point to a local directory. The local directory needs to be empty and readale/writable", "change": "Change" }, "driveLetter": { "name": "Drive letter", - "info": "Set the drive letter for the virtual drive. Only available drive letters will show up." + "info": "Set the drive letter for the virtual drive. Only available drive letters will show up" }, "browse": { "name": "Browse", - "info": "Browse the virtual drive using your default explorer." + "info": "Browse the virtual drive using your default explorer" }, "cache": { "name": "Cache", - "info": "Caching significantly speeds up browsing your virtual drive." + "info": "Caching significantly speeds up browsing your virtual drive" }, "cacheSize": { "name": "Cache size", - "info": "We recommend setting the cache size as high as possible. The client will use your local disk to significantly speed up the virtual drive." + "info": "We recommend setting the cache size as high as possible. The client will use your local disk to significantly speed up the virtual drive" } }, "dialogs": { @@ -1181,39 +1191,39 @@ }, "enabled": { "name": "Enabled", - "info": "Enable or disable the WebDAV server." + "info": "Enable or disable the WebDAV server" }, "protocol": { "name": "HTTP Protocol", - "info": "Change the HTTP Protocol." + "info": "Change the HTTP Protocol" }, "hostname": { "name": "Hostname", - "info": "Change the hostname the server should listen on." + "info": "Change the hostname the server should listen on" }, "port": { "name": "Port", - "info": "Change the port the server should listen on." + "info": "Change the port the server should listen on" }, "authMode": { "name": "Authentication mode", - "info": "Change the authentication mode." + "info": "Change the authentication mode" }, "username": { "name": "Username", - "info": "Change the HTTP authentication username. This is should not be your Filen email or nickname." + "info": "Change the HTTP authentication username. This is should not be your Filen email or nickname" }, "password": { "name": "Password", - "info": "Change the HTTP authentication password. This is should not be your Filen password." + "info": "Change the HTTP authentication password. This is should not be your Filen password" }, "proxyMode": { "name": "Proxy mode", - "info": "Enable the proxy mode." + "info": "Enable the proxy mode" }, "connect": { "name": "Connect", - "info": "Copy the WebDAV URL." + "info": "Copy the WebDAV URL" } }, "dialogs": { @@ -1236,31 +1246,31 @@ }, "enabled": { "name": "Enabled", - "info": "Enable or disable the S3 server." + "info": "Enable or disable the S3 server" }, "protocol": { "name": "HTTP Protocol", - "info": "Change the HTTP Protocol." + "info": "Change the HTTP Protocol" }, "hostname": { "name": "Hostname", - "info": "Change the hostname the server should listen on." + "info": "Change the hostname the server should listen on" }, "port": { "name": "Port", - "info": "Change the port the server should listen on." + "info": "Change the port the server should listen on" }, "accessKeyId": { "name": "AccessKeyId", - "info": "Change the AccessKeyId. This is should not be your Filen email or nickname." + "info": "Change the AccessKeyId. This is should not be your Filen email or nickname" }, "secretKeyId": { "name": "SecretKeyId", - "info": "Change the SecretKeyId. This is should not be your Filen password." + "info": "Change the SecretKeyId. This is should not be your Filen password" }, "connect": { "name": "Connect", - "info": "Copy the WebDAV URL." + "info": "Copy the WebDAV URL" } }, "dialogs": { @@ -1277,10 +1287,12 @@ } }, "syncs": { - "events": "Events", - "ignored": "Ignored", - "issues": "Issues", + "eventsTitle": "Events", + "ignoredTitle": "Ignored", + "issuesTitle": "Issues", "settingsTitle": "Settings", + "transfersTitle": "Transfers", + "noTransfers": "No active transfers", "noEventsYet": "No events yet", "nothingIgnored": "No files or directories ignored", "noIssues": "No issues", @@ -1289,6 +1301,20 @@ "description": "You can sync your local directories with the cloud or cloud directories with your device", "create": "Create" }, + "events": { + "createLocalDirectory": "{{name}} created locally", + "renameLocalDirectory": "{{name}} renamed locally", + "deleteLocalDirectory": "{{name}} deleted locally", + "createRemoteDirectory": "{{name}} created in the cloud", + "renameRemoteDirectory": "{{name}} renamed in the cloud", + "deleteRemoteDirectory": "{{name}} deleted from the cloud", + "deleteLocalFile": "{{name}} deleted locally", + "deleteRemoteFile": "{{name}} deleted from the cloud", + "download": "{{name}} downloaded from the cloud", + "upload": "{{name}} uploaded to the cloud", + "renameLocalFile": "{{name}} renamed locally", + "renameRemoteFile": "{{name}} renamed in the cloud" + }, "settings": { "sections": { "delete": { @@ -1299,6 +1325,24 @@ "pause": { "name": "Pause", "info": "Pause the sync" + }, + "mode": { + "name": "Sync mode", + "info": "Change the sync mode" + }, + "forceSync": { + "name": "Force sync", + "info": "Reset the internal cache and force re-scanning of the local and cloud directory", + "forceSync": "Force sync" + }, + "excludeDotFiles": { + "name": "Exclude dot files", + "info": "Toggle dot file/path exclusion (recommended)" + }, + "filenIgnore": { + "name": ".filenignore", + "info": "Edit an ignore file for local or cloud exclusions. Works just like a .gitignore file", + "edit": "Edit" } } }, @@ -1307,6 +1351,22 @@ "title": "Delete sync", "description": "Are you sure you want to delete this sync?", "continue": "Delete" + }, + "filenIgnore": { + "title": ".filenignore", + "save": "Save" + } + }, + "ignored": { + "types": { + "dotFile": "Dot file", + "defaultIgnore": "Ignored by default", + "empty": "Empty" + }, + "reasons": { + "dotFile": "File or directory is inside a dot path", + "defaultIgnore": "File or directory is inside a path that is ignored by default", + "empty": "File is empty" } } } diff --git a/package-lock.json b/package-lock.json index fe34b97a..ba7a8eb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,9 +11,9 @@ "@alptugidin/react-circular-progress-bar": "^1.1.2", "@emoji-mart/data": "^1.1.2", "@emoji-mart/react": "^1.1.1", - "@filen/desktop": "^0.1.23", + "@filen/desktop": "^0.1.25", "@filen/sdk": "^0.1.134", - "@filen/sync": "^0.1.5", + "@filen/sync": "^0.1.8", "@million/lint": "^1.0.0-rc.7", "@radix-ui/react-alert-dialog": "^1.0.5", "@radix-ui/react-avatar": "^1.0.4", @@ -66,7 +66,7 @@ "lodash": "^4.17.21", "lucide-react": "^0.344.0", "mammoth": "^1.7.2", - "million": "^3.1.4", + "million": "^3.1.11", "native-file-system-adapter": "^3.0.1", "pdfjs-dist": "^4.0.379", "react": "^18.3.1", @@ -171,7 +171,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true, "engines": { "node": ">=10" }, @@ -1066,7 +1065,7 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, + "devOptional": true, "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -1078,7 +1077,7 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, + "devOptional": true, "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -1584,15 +1583,16 @@ } }, "node_modules/@filen/desktop": { - "version": "0.1.23", - "resolved": "https://registry.npmjs.org/@filen/desktop/-/desktop-0.1.23.tgz", - "integrity": "sha512-EZIMXW9Kw/caVkWOI/plt/bwCqVQTXceBs0kK4tUCIHLIng4r5rFHADbTu94XqTT24eqi90WaUbAHCqBUwDjyw==", + "version": "0.1.25", + "resolved": "https://registry.npmjs.org/@filen/desktop/-/desktop-0.1.25.tgz", + "integrity": "sha512-kfrc5DR8jCvA59LRlOJBm3xw9ZdpFMGgnS7zQeN3IfZtWBB7VGzqQBd20dogvNj902PhNCD7lAVaVU7A2+cKgw==", "dependencies": { - "@filen/s3": "^0.2.12", + "@filen/s3": "^0.2.13", "@filen/sdk": "^0.1.134", - "@filen/sync": "^0.1.5", + "@filen/sync": "^0.1.8", "@filen/web": "^0.1.2", - "@filen/webdav": "^0.2.19", + "@filen/webdav": "^0.2.20", + "axios": "^1.7.2", "diskusage-ng": "^1.0.4", "find-free-ports": "^3.1.1", "fs-extra": "^11.2.0", @@ -1607,9 +1607,9 @@ } }, "node_modules/@filen/s3": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@filen/s3/-/s3-0.2.12.tgz", - "integrity": "sha512-62DdWpzoKofcK+iinuKVF1BlNXAFAu88RhxZAQO4LEDI83IqFRkijJGuZmAmLCdeV83HNAzmzaJU9V8KQTd9AQ==", + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/@filen/s3/-/s3-0.2.13.tgz", + "integrity": "sha512-HeVbvgQ17W3vo4vsB7j6VKvNDrWH2gafZuA0Mt1IcQXeQKkWriIgouN9i5NALc6f8IXgoXU8SPVBEqzsb5sTXw==", "dependencies": { "@filen/sdk": "^0.1.134", "aws-sdk": "^2.1636.0", @@ -1650,9 +1650,9 @@ } }, "node_modules/@filen/sync": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@filen/sync/-/sync-0.1.5.tgz", - "integrity": "sha512-17+p+2e0YyZ+qTAE+GOK7ylPR5fpNh3U2jGLJXagqua8bG4Qe7KxNyTsV5Y/77WDAWFS0yv3NYf2K/0DqKn6KA==", + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@filen/sync/-/sync-0.1.8.tgz", + "integrity": "sha512-we/aqkv0P6Rf14JaFSlKbyuCNOX2zoUpH+h83EAuVSjjwI4HMG+dHnVu3rG5LwFvsYavyKeaIrd4K/qS9lLGRg==", "dependencies": { "@filen/sdk": "^0.1.134", "@parcel/watcher": "^2.4.1", @@ -1780,9 +1780,9 @@ "extraneous": true }, "node_modules/@filen/webdav": { - "version": "0.2.19", - "resolved": "https://registry.npmjs.org/@filen/webdav/-/webdav-0.2.19.tgz", - "integrity": "sha512-DuTIHOWJrkWL4F4uPttpiS840ZYbFStXUOL5QqOtdCXrYeru4MyHmXgzB2XbR4TAyy+/uBoGVjSbN4j1UIJy3Q==", + "version": "0.2.20", + "resolved": "https://registry.npmjs.org/@filen/webdav/-/webdav-0.2.20.tgz", + "integrity": "sha512-b+M0XSYAaMACmCBE6Nb4gn/lmR8stZ/ts61WOy8FDEbFGSO7vOnxfI2ibyfKS8rtKYRAlgXhqqtSvb4Gt4lfjA==", "dependencies": { "@filen/sdk": "^0.1.134", "body-parser": "^1.20.2", @@ -1793,7 +1793,7 @@ "mime-types": "^2.1.35", "node-cache": "^5.1.2", "selfsigned": "^2.4.1", - "uuid": "^9.0.1", + "uuid": "^10.0.0", "xml-js-builder": "^1.0.3", "xml2js": "^0.6.2" }, @@ -1801,6 +1801,18 @@ "node": ">=18" } }, + "node_modules/@filen/webdav/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@floating-ui/core": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", @@ -1902,7 +1914,6 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -1919,7 +1930,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, "engines": { "node": ">=12" }, @@ -1931,7 +1941,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, "dependencies": { "ansi-regex": "^6.0.1" }, @@ -2273,6 +2282,166 @@ "lint": "dist/wizard/index.js" } }, + "node_modules/@next/env": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.5.tgz", + "integrity": "sha512-/zZGkrTOsraVfYjGP8uM0p6r0BDT6xWpkjdVbcz66PJVSpwXX3yNiRycxAuDfBKGWBrZBXRuK/YVlkNgxHGwmA==", + "dev": true, + "peer": true + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.5.tgz", + "integrity": "sha512-/9zVxJ+K9lrzSGli1///ujyRfon/ZneeZ+v4ptpiPoOU+GKZnm8Wj8ELWU1Pm7GHltYRBklmXMTUqM/DqQ99FQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.5.tgz", + "integrity": "sha512-vXHOPCwfDe9qLDuq7U1OYM2wUY+KQ4Ex6ozwsKxp26BlJ6XXbHleOUldenM67JRyBfVjv371oneEvYd3H2gNSA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.5.tgz", + "integrity": "sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.5.tgz", + "integrity": "sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.5.tgz", + "integrity": "sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.5.tgz", + "integrity": "sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.5.tgz", + "integrity": "sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.5.tgz", + "integrity": "sha512-Igh9ZlxwvCDsu6438FXlQTHlRno4gFpJzqPjSIBZooD22tKeI4fE/YMRoHVJHmrQ2P5YL1DoZ0qaOKkbeFWeMg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.5.tgz", + "integrity": "sha512-tEQ7oinq1/CjSG9uSTerca3v4AZ+dFa+4Yu6ihaG8Ud8ddqLQgFGcnwYls13H5X5CPDPZJdYxyeMui6muOLd4g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, "node_modules/@nextjournal/lang-clojure": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@nextjournal/lang-clojure/-/lang-clojure-1.0.0.tgz", @@ -2294,7 +2463,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -2307,7 +2475,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, "engines": { "node": ">= 8" } @@ -2316,7 +2483,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -2600,7 +2766,6 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, "optional": true, "engines": { "node": ">=14" @@ -4338,6 +4503,17 @@ "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", "dev": true }, + "node_modules/@swc/helpers": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", + "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==", + "dev": true, + "peer": true, + "dependencies": { + "@swc/counter": "^0.1.3", + "tslib": "^2.4.0" + } + }, "node_modules/@swc/types": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.5.tgz", @@ -4701,25 +4877,25 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true + "devOptional": true }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true + "devOptional": true }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true + "devOptional": true }, "node_modules/@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true + "devOptional": true }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -4905,8 +5081,7 @@ "node_modules/@types/prop-types": { "version": "15.7.11", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", - "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==", - "dev": true + "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" }, "node_modules/@types/quill": { "version": "1.3.10", @@ -4920,7 +5095,6 @@ "version": "18.2.63", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.63.tgz", "integrity": "sha512-ppaqODhs15PYL2nGUOaOu2RSCCB4Difu4UFrP4I3NHLloXC/ESQzQMi9nvjfT1+rudd0d2L3fQPJxRSey+rGlQ==", - "dev": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -4931,7 +5105,7 @@ "version": "18.2.20", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.20.tgz", "integrity": "sha512-HXN/biJY8nv20Cn9ZbCFq3liERd4CozVZmKbaiZ9KiKTrWqsP7eoGDO6OOGvJQwoVFuiXaiJ7nBBjiFFbRmQMQ==", - "dev": true, + "devOptional": true, "dependencies": { "@types/react": "*" } @@ -4948,8 +5122,7 @@ "node_modules/@types/scheduler": { "version": "0.16.8", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", - "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", - "dev": true + "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==" }, "node_modules/@types/semver": { "version": "7.5.8", @@ -5522,7 +5695,7 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.4.0" } @@ -5652,8 +5825,7 @@ "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" }, "node_modules/anymatch": { "version": "3.1.3", @@ -5709,8 +5881,7 @@ "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "dev": true + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" }, "node_modules/argparse": { "version": "2.0.1", @@ -5835,9 +6006,9 @@ } }, "node_modules/aws-sdk": { - "version": "2.1646.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1646.0.tgz", - "integrity": "sha512-PAvDiR8ow3zjO0T5HMda04kXIzQ5e1zeWxWGSUodRwu9W569gZPBnqzcPX3PJFNAKBZnZBdbNgsci1g2nXCcBg==", + "version": "2.1665.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1665.0.tgz", + "integrity": "sha512-IhEcdGmiplF3l/pCROxEYIdi0s+LZ2VkbMAq3RgoXTHxY5cgqVRNaqsEsgIHev2Clxa9V08HttnIERTIUqb1+Q==", "hasInstallScript": true, "dependencies": { "buffer": "4.9.2", @@ -5901,9 +6072,9 @@ } }, "node_modules/axios": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", - "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", + "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -5935,8 +6106,7 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "devOptional": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/base64-arraybuffer": { "version": "0.1.4", @@ -6130,7 +6300,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -6349,6 +6518,19 @@ "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==", "dev": true }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dev": true, + "peer": true, + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, "node_modules/bytes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", @@ -6425,7 +6607,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true, "engines": { "node": ">= 6" } @@ -6879,6 +7060,13 @@ "node": ">=8" } }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "dev": true, + "peer": true + }, "node_modules/clipboardy": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-3.0.0.tgz", @@ -7069,7 +7257,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, "engines": { "node": ">= 6" } @@ -7285,7 +7472,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true + "devOptional": true }, "node_modules/crelt": { "version": "1.0.6", @@ -7322,7 +7509,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -7391,7 +7577,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, "bin": { "cssesc": "bin/cssesc" }, @@ -7402,8 +7587,7 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/date-fns": { "version": "2.30.0", @@ -7421,9 +7605,9 @@ } }, "node_modules/dayjs": { - "version": "1.11.11", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz", - "integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==" + "version": "1.11.12", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.12.tgz", + "integrity": "sha512-Rt2g+nTbLlDWZTwwrIXjy9MeiZmSDI375FvZs72ngxx8PDC6YXOeR3q5LAuPzjZQxhiWdRKac7RKV+YyQYfYIg==" }, "node_modules/debug": { "version": "4.3.4", @@ -7648,8 +7832,7 @@ "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "dev": true + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" }, "node_modules/diff": { "version": "5.2.0", @@ -7716,8 +7899,7 @@ "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" }, "node_modules/doctrine": { "version": "3.0.0", @@ -7804,8 +7986,7 @@ "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, "node_modules/ee-first": { "version": "1.1.1", @@ -7862,8 +8043,7 @@ "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" }, "node_modules/emojibase-regex": { "version": "15.3.0", @@ -8466,7 +8646,6 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -8482,7 +8661,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -8521,7 +8699,6 @@ "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "dev": true, "dependencies": { "reusify": "^1.0.4" } @@ -8745,7 +8922,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", - "dev": true, "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" @@ -9004,7 +9180,6 @@ "version": "10.4.1", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", - "dev": true, "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", @@ -9081,7 +9256,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -9093,7 +9267,6 @@ "version": "9.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", - "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -10034,7 +10207,6 @@ "version": "2.13.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", - "dev": true, "dependencies": { "hasown": "^2.0.0" }, @@ -10260,8 +10432,7 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "node_modules/isomorphic-fetch": { "version": "3.0.0", @@ -10285,7 +10456,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.1.2.tgz", "integrity": "sha512-kWmLKn2tRtfYMF/BakihVVRzBKOxz4gJMiL2Rj91WnAB5TPZumSH99R/Yf1qE1u4uRimvCSJfm6hnxohXeEXjQ==", - "dev": true, "dependencies": { "@isaacs/cliui": "^8.0.2" }, @@ -10303,7 +10473,6 @@ "version": "1.21.0", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", - "dev": true, "bin": { "jiti": "bin/jiti.js" } @@ -10621,7 +10790,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", - "dev": true, "engines": { "node": ">=10" } @@ -10629,8 +10797,7 @@ "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, "node_modules/localforage": { "version": "1.10.0", @@ -10795,7 +10962,7 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true + "devOptional": true }, "node_modules/mammoth": { "version": "1.7.2", @@ -11225,7 +11392,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, "engines": { "node": ">= 8" } @@ -11805,62 +11971,24 @@ "dev": true }, "node_modules/million": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/million/-/million-3.1.4.tgz", - "integrity": "sha512-ZEg5EX8leO9TQK1S6Sxas7BTzo7CcNMZD9E/9VdqI3vJpgPxSMS7IEq6VO4i/hgZWC2m2b2mBTAx/Ms98hdjXg==", + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/million/-/million-3.1.11.tgz", + "integrity": "sha512-6Vh1s0da0PzSqbbp9Zd8yMTIkOWnvBU4vNJCMHTZPXaY3fZ5h+N7s5croS/RBgjJIHz3WQZnvyNBQz7gQ6cqJg==", "dependencies": { "@babel/core": "^7.23.7", "@babel/types": "^7.23.6", - "@million/install": "^0.0.5", "@rollup/pluginutils": "^5.1.0", "kleur": "^4.1.5", "undici": "^6.3.0", "unplugin": "^1.6.0" }, "bin": { - "million": "packages/cli/dist/index.js" + "million": "cli.js" }, "funding": { "url": "https://github.com/sponsors/aidenybai" } }, - "node_modules/million/node_modules/@million/install": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/@million/install/-/install-0.0.5.tgz", - "integrity": "sha512-9KYVwMT6yEX0k/DMo8cOQNxeFdZme/yTqncPwXgvoQ0qUAUkulvm0ivRHTYrsuPNrCA6lqsJD005D1hhuI2fGQ==", - "dependencies": { - "@antfu/ni": "^0.21.12", - "@axiomhq/js": "1.0.0-rc.3", - "@babel/core": "^7.24.5", - "@babel/types": "^7.23.6", - "@clack/prompts": "^0.7.0", - "cli-high": "^0.4.1", - "diff": "^5.1.0", - "nanoid": "^5.0.7", - "posthog-node": "^3.6.3", - "xycolors": "^0.1.1" - }, - "bin": { - "install": "bin/index.js" - } - }, - "node_modules/million/node_modules/nanoid": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.7.tgz", - "integrity": "sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.js" - }, - "engines": { - "node": "^18 || >=20" - } - }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -11947,7 +12075,6 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, "engines": { "node": ">=16 || 14 >=14.17" } @@ -11998,7 +12125,6 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", @@ -12015,7 +12141,6 @@ "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true, "funding": [ { "type": "github", @@ -12064,6 +12189,86 @@ "node": ">= 0.6" } }, + "node_modules/next": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/next/-/next-14.2.5.tgz", + "integrity": "sha512-0f8aRfBVL+mpzfBjYfQuLWh2WyAwtJXCRfkPF4UJ5qd2YwrHczsrSzXU4tRMV0OAxR8ZJZWPFn6uhSC56UTsLA==", + "dev": true, + "peer": true, + "dependencies": { + "@next/env": "14.2.5", + "@swc/helpers": "0.5.5", + "busboy": "1.6.0", + "caniuse-lite": "^1.0.30001579", + "graceful-fs": "^4.2.11", + "postcss": "8.4.31", + "styled-jsx": "5.1.1" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=18.17.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "14.2.5", + "@next/swc-darwin-x64": "14.2.5", + "@next/swc-linux-arm64-gnu": "14.2.5", + "@next/swc-linux-arm64-musl": "14.2.5", + "@next/swc-linux-x64-gnu": "14.2.5", + "@next/swc-linux-x64-musl": "14.2.5", + "@next/swc-win32-arm64-msvc": "14.2.5", + "@next/swc-win32-ia32-msvc": "14.2.5", + "@next/swc-win32-x64-msvc": "14.2.5" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.41.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true, + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/nice-napi": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", @@ -12330,7 +12535,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true, "engines": { "node": ">= 6" } @@ -12673,7 +12877,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "engines": { "node": ">=8" } @@ -12681,14 +12884,12 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "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, "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -12704,7 +12905,6 @@ "version": "10.2.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", - "dev": true, "engines": { "node": "14 || >=16.14" } @@ -12792,7 +12992,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -12801,7 +13000,6 @@ "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", - "dev": true, "engines": { "node": ">= 6" } @@ -12838,7 +13036,6 @@ "version": "8.4.38", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", - "dev": true, "funding": [ { "type": "opencollective", @@ -12866,7 +13063,6 @@ "version": "15.1.0", "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "dev": true, "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", @@ -12883,7 +13079,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", - "dev": true, "dependencies": { "camelcase-css": "^2.0.1" }, @@ -12902,7 +13097,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", - "dev": true, "funding": [ { "type": "opencollective", @@ -12937,7 +13131,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz", "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==", - "dev": true, "engines": { "node": ">=14" }, @@ -12949,7 +13142,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", - "dev": true, "dependencies": { "postcss-selector-parser": "^6.0.11" }, @@ -12968,7 +13160,6 @@ "version": "6.0.15", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz", "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==", - "dev": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -12980,8 +13171,7 @@ "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, "node_modules/posthog-node": { "version": "3.6.3", @@ -13182,7 +13372,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -13608,7 +13797,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, "dependencies": { "pify": "^2.3.0" } @@ -13944,7 +14132,6 @@ "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", @@ -13997,7 +14184,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -14053,7 +14239,7 @@ "version": "4.14.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.0.tgz", "integrity": "sha512-Qe7w62TyawbDzB4yt32R0+AbIo6m1/sqO7UPzFS8Z/ksL5mrfhA0v4CavfdmFav3D+ub4QeAgsGEe84DoWe/nQ==", - "dev": true, + "devOptional": true, "dependencies": { "@types/estree": "1.0.5" }, @@ -14098,7 +14284,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -14524,7 +14709,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -14536,7 +14720,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "engines": { "node": ">=8" } @@ -14562,7 +14745,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, "engines": { "node": ">=14" }, @@ -14752,9 +14934,9 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/socket.io-parser": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.3.tgz", - "integrity": "sha512-qOg87q1PMWWTeO01768Yh9ogn7chB9zkKtQnya41Y355S0UmpXgpcrFwAgjYJxu9BdKug5r5e9YtVSeWhKBUZg==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.4.tgz", + "integrity": "sha512-z/pFQB3x+EZldRRzORYW1vwVO8m/3ILkswtnpoeU6Ve3cbMWkmHEWDAVJn4QJtchiiFTo5j7UG2QvwxvaA9vow==", "dependencies": { "component-emitter": "~1.3.0", "debug": "~3.1.0", @@ -14792,7 +14974,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -14848,6 +15029,16 @@ "xtend": "^4.0.2" } }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -14861,7 +15052,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -14879,7 +15069,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -14892,14 +15081,12 @@ "node_modules/string-width-cjs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/string-width/node_modules/ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, "engines": { "node": ">=12" }, @@ -14911,7 +15098,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, "dependencies": { "ansi-regex": "^6.0.1" }, @@ -14951,7 +15137,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -14998,11 +15183,34 @@ "inline-style-parser": "0.2.3" } }, + "node_modules/styled-jsx": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", + "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", + "dev": true, + "peer": true, + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, "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, "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", @@ -15066,7 +15274,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -15096,7 +15303,6 @@ "version": "3.4.1", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", - "dev": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -15173,7 +15379,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, "dependencies": { "any-promise": "^1.0.0" } @@ -15182,7 +15387,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, "dependencies": { "thenify": ">= 3.1.0 < 4" }, @@ -15343,14 +15547,13 @@ "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, + "devOptional": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -15393,13 +15596,13 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true + "devOptional": true }, "node_modules/ts-node/node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.3.1" } @@ -15482,7 +15685,7 @@ "version": "5.3.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", - "dev": true, + "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -15878,7 +16081,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true + "devOptional": true }, "node_modules/vary": { "version": "1.1.2", @@ -16144,7 +16347,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -16227,7 +16429,6 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -16245,7 +16446,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -16261,14 +16461,12 @@ "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -16282,7 +16480,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, "engines": { "node": ">=12" }, @@ -16294,7 +16491,6 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, "engines": { "node": ">=12" }, @@ -16306,7 +16502,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, "dependencies": { "ansi-regex": "^6.0.1" }, @@ -16439,7 +16634,6 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.0.tgz", "integrity": "sha512-j9iR8g+/t0lArF4V6NE/QCfT+CO7iLqrXAHZbJdo+LfjqP1vR8Fg5bSiaq6Q2lOD1AUEVrEVIgABvBFYojJVYQ==", - "dev": true, "bin": { "yaml": "bin.mjs" }, @@ -16573,7 +16767,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, + "devOptional": true, "engines": { "node": ">=6" } diff --git a/package.json b/package.json index dcfc4ab7..c584d7f4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@filen/web", "private": false, - "version": "0.1.5", + "version": "0.1.6", "type": "module", "description": "Filen Web App", "scripts": { @@ -22,9 +22,9 @@ "@alptugidin/react-circular-progress-bar": "^1.1.2", "@emoji-mart/data": "^1.1.2", "@emoji-mart/react": "^1.1.1", - "@filen/desktop": "^0.1.23", + "@filen/desktop": "^0.1.25", "@filen/sdk": "^0.1.134", - "@filen/sync": "^0.1.5", + "@filen/sync": "^0.1.8", "@million/lint": "^1.0.0-rc.7", "@radix-ui/react-alert-dialog": "^1.0.5", "@radix-ui/react-avatar": "^1.0.4", @@ -77,7 +77,7 @@ "lodash": "^4.17.21", "lucide-react": "^0.344.0", "mammoth": "^1.7.2", - "million": "^3.1.4", + "million": "^3.1.11", "native-file-system-adapter": "^3.0.1", "pdfjs-dist": "^4.0.379", "react": "^18.3.1", diff --git a/src/assets/fileExtensionIcons/index.tsx b/src/assets/fileExtensionIcons/index.tsx index 5510866f..5df2673b 100644 --- a/src/assets/fileExtensionIcons/index.tsx +++ b/src/assets/fileExtensionIcons/index.tsx @@ -243,7 +243,8 @@ export const ColoredFolderSVGIcon = memo( height: height ? height : "1em", verticalAlign: "middle", fill: "currentcolor", - overflow: "hidden" + overflow: "hidden", + flexShrink: 0 }} className="dragselect-start-disallowed" viewBox="0 0 1228 1024" diff --git a/src/components/chats/conversation/input.tsx b/src/components/chats/conversation/input.tsx index 00cf845a..d721d6be 100644 --- a/src/components/chats/conversation/input.tsx +++ b/src/components/chats/conversation/input.tsx @@ -841,9 +841,11 @@ export const Input = memo(({ conversation }: { conversation: ChatConversation }) eventEmitter.emit("attachFilesToChat", filesWithLinkUUIDs) } catch (e) { - console.error(e) + if (e instanceof Error && !e.message.toLowerCase().includes("abort")) { + console.error(e) - errorToast((e as unknown as Error).message ?? (e as unknown as Error).toString()) + errorToast((e as unknown as Error).message ?? (e as unknown as Error).toString()) + } } finally { e.target.value = "" diff --git a/src/components/desktopHandler.tsx b/src/components/desktopHandler.tsx index ec71e616..7f490289 100644 --- a/src/components/desktopHandler.tsx +++ b/src/components/desktopHandler.tsx @@ -7,6 +7,7 @@ import { Semaphore } from "@/lib/semaphore" import { isVirtualDriveMounted } from "./mounts/virtualDrive" import { isWebDAVOnline } from "./mounts/webdav" import { isS3Online } from "./mounts/s3" +import { useMountsStore } from "@/stores/mounts.store" export const updateDesktopConfigMutex = new Semaphore(1) @@ -14,6 +15,7 @@ export const DesktopHandler = memo(() => { const [authed] = useLocalStorage("authed", false) const [desktopConfig, setDesktopConfig] = useDesktopConfig() const lastDesktopConfigRef = useRef("") + const { setEnablingS3, setEnablingVirtualDrive, setEnablingWebDAV } = useMountsStore() const currentDesktopConfigStringified = useMemo(() => { return JSON.stringify(desktopConfig) @@ -24,6 +26,8 @@ export const DesktopHandler = memo(() => { return } + setEnablingVirtualDrive(true) + try { await window.desktopAPI.restartVirtualDrive() @@ -38,14 +42,18 @@ export const DesktopHandler = memo(() => { enabled: false } })) + } finally { + setEnablingVirtualDrive(false) } - }, [setDesktopConfig, authed]) + }, [setDesktopConfig, authed, setEnablingVirtualDrive]) const startWebDAV = useCallback(async () => { if (!authed) { return } + setEnablingWebDAV(true) + try { await window.desktopAPI.restartWebDAVServer() @@ -60,14 +68,18 @@ export const DesktopHandler = memo(() => { enabled: false } })) + } finally { + setEnablingWebDAV(false) } - }, [setDesktopConfig, authed]) + }, [setDesktopConfig, authed, setEnablingWebDAV]) const startS3 = useCallback(async () => { if (!authed) { return } + setEnablingS3(true) + try { await window.desktopAPI.restartS3Server() @@ -82,8 +94,10 @@ export const DesktopHandler = memo(() => { enabled: false } })) + } finally { + setEnablingS3(false) } - }, [setDesktopConfig, authed]) + }, [setDesktopConfig, authed, setEnablingS3]) useEffect(() => { ;(async () => { @@ -98,25 +112,13 @@ export const DesktopHandler = memo(() => { await updateDesktopConfigMutex.acquire() try { - const [isSyncActive, { mounted: virtualDriveMounted }, { online: webdavOnline }, { online: s3Online }] = await Promise.all([ - window.desktopAPI.isSyncActive(), + const [{ mounted: virtualDriveMounted }, { online: webdavOnline }, { online: s3Online }] = await Promise.all([ isVirtualDriveMounted(), isWebDAVOnline(), isS3Online() ]) await Promise.all([ - isSyncActive - ? window.desktopAPI.forwardSyncMessage({ - type: "updateSyncPairs", - data: { - pairs: desktopConfig.syncConfig.syncPairs, - resetCache: false - } - }) - : desktopConfig.syncConfig.syncPairs.length > 0 - ? window.desktopAPI.restartSync() - : Promise.resolve(), desktopConfig.virtualDriveConfig.enabled && !virtualDriveMounted ? startVirtualDrive() : Promise.resolve(), desktopConfig.webdavConfig.enabled && !webdavOnline ? startWebDAV() : Promise.resolve(), desktopConfig.s3Config.enabled && !s3Online ? startS3() : Promise.resolve() diff --git a/src/components/desktopListener.tsx b/src/components/desktopListener.tsx index 1234e797..e75a4117 100644 --- a/src/components/desktopListener.tsx +++ b/src/components/desktopListener.tsx @@ -1,12 +1,118 @@ -import { memo, useEffect } from "react" +import { memo, useEffect, useRef } from "react" import { IS_DESKTOP } from "@/constants" import { useLocalStorage } from "@uidotdev/usehooks" import { useSyncsStore, type Transfer, type GeneralError } from "@/stores/syncs.store" import pathModule from "path" +import throttle from "lodash/throttle" +import { calcTimeLeft, calcSpeed, getTimeRemaining } from "./transfers/utils" +import { useTranslation } from "react-i18next" export const DesktopListener = memo(() => { const [authed] = useLocalStorage("authed", false) - const { setTransferEvents, setCycleState, setTransfers, setErrors, setTaskErrors, setLocalIgnored, setRemoteIgnored } = useSyncsStore() + const { + setTransferEvents, + setCycleState, + setTransfers, + setErrors, + setTaskErrors, + setLocalIgnored, + setRemoteIgnored, + setRemainingReadable, + setProgress, + setRemaining, + setSpeed, + transfers + } = useSyncsStore() + const bytesSent = useRef>({}).current + const allBytes = useRef>({}).current + const progressStarted = useRef>({}).current + const { t } = useTranslation() + + const updateProgress = useRef( + throttle((syncUUID: string) => { + if (!bytesSent[syncUUID]) { + bytesSent[syncUUID] = 0 + } + + if (!allBytes[syncUUID]) { + allBytes[syncUUID] = 0 + } + + if (!progressStarted[syncUUID]) { + progressStarted[syncUUID] = -1 + } + + const now = Date.now() + const transferRemaining = calcTimeLeft(bytesSent[syncUUID]!, allBytes[syncUUID]!, progressStarted[syncUUID]!) + const transferPercent = (bytesSent[syncUUID]! / allBytes[syncUUID]!) * 100 + const transferSpeed = calcSpeed(now, progressStarted[syncUUID]!, bytesSent[syncUUID]!) + + setRemaining(prev => ({ + ...prev, + [syncUUID]: transferRemaining + })) + + setSpeed(prev => ({ + ...prev, + [syncUUID]: transferSpeed + })) + + setProgress(prev => ({ + ...prev, + [syncUUID]: isNaN(transferPercent) ? 0 : transferPercent >= 100 ? 100 : transferPercent + })) + + const remainingReadable = getTimeRemaining(now + transferRemaining * 1000) + + if (remainingReadable.total <= 1 || remainingReadable.seconds <= 1) { + remainingReadable.total = 1 + remainingReadable.days = 0 + remainingReadable.hours = 0 + remainingReadable.minutes = 0 + remainingReadable.seconds = 1 + } + + setRemainingReadable(prev => ({ + ...prev, + [syncUUID]: t("transfers.remaining", { + time: + (remainingReadable.days > 0 ? remainingReadable.days + "d " : "") + + (remainingReadable.hours > 0 ? remainingReadable.hours + "h " : "") + + (remainingReadable.minutes > 0 ? remainingReadable.minutes + "m " : "") + + (remainingReadable.seconds > 0 ? remainingReadable.seconds + "s " : "") + }) + })) + }, 100) + ).current + + useEffect(() => { + for (const syncUUID in transfers) { + const ongoingTransfers = transfers[syncUUID]!.filter( + transfer => transfer.state === "queued" || transfer.state === "started" || transfer.state === "paused" + ) + + if (ongoingTransfers.length <= 0) { + bytesSent[syncUUID] = 0 + progressStarted[syncUUID] = -1 + allBytes[syncUUID] = 0 + + setRemaining(prev => ({ + ...prev, + [syncUUID]: 0 + })) + + setSpeed(prev => ({ + ...prev, + [syncUUID]: 0 + })) + + setProgress(prev => ({ + ...prev, + [syncUUID]: 0 + })) + } + } + }, [transfers, setSpeed, setRemaining, setProgress, allBytes, bytesSent, progressStarted]) useEffect(() => { let syncMessageListener: ReturnType | null = null @@ -118,6 +224,18 @@ export const DesktopListener = memo(() => { ? [transfer, ...prev[message.syncPair.uuid]!] : [transfer] })) + + if (!progressStarted[message.syncPair.uuid]) { + progressStarted[message.syncPair.uuid] = -1 + } + + if (progressStarted[message.syncPair.uuid] === -1) { + progressStarted[message.syncPair.uuid] = now + } else { + if (now < progressStarted[message.syncPair.uuid]!) { + progressStarted[message.syncPair.uuid] = now + } + } } else if (message.data.type === "started") { const { data } = message @@ -136,6 +254,12 @@ export const DesktopListener = memo(() => { ) : [] })) + + if (!allBytes[message.syncPair.uuid]) { + allBytes[message.syncPair.uuid] = -1 + } + + allBytes[message.syncPair.uuid]! += data.size } else if (message.data.type === "progress") { const { data } = message @@ -153,6 +277,12 @@ export const DesktopListener = memo(() => { ) : [] })) + + if (!bytesSent[message.syncPair.uuid]) { + bytesSent[message.syncPair.uuid] = -1 + } + + bytesSent[message.syncPair.uuid]! += data.bytes } else if (message.data.type === "finished") { setTransfers(prev => ({ ...prev, @@ -175,7 +305,17 @@ export const DesktopListener = memo(() => { ) : [] })) + + if (!allBytes[message.syncPair.uuid]) { + allBytes[message.syncPair.uuid] = -1 + } + + if (allBytes[message.syncPair.uuid]! >= message.data.size) { + allBytes[message.syncPair.uuid]! -= message.data.size + } } + + updateProgress(message.syncPair.uuid) } else { setTransferEvents(prev => ({ ...prev, @@ -249,7 +389,20 @@ export const DesktopListener = memo(() => { return () => { syncMessageListener?.remove() } - }, [setTransferEvents, setCycleState, setTransfers, authed, setErrors, setTaskErrors, setLocalIgnored, setRemoteIgnored]) + }, [ + setTransferEvents, + setCycleState, + setTransfers, + authed, + setErrors, + setTaskErrors, + setLocalIgnored, + setRemoteIgnored, + updateProgress, + allBytes, + progressStarted, + bytesSent + ]) return null }) diff --git a/src/components/dialogs/createSync.tsx b/src/components/dialogs/createSync.tsx index 292c51c5..4abd765f 100644 --- a/src/components/dialogs/createSync.tsx +++ b/src/components/dialogs/createSync.tsx @@ -22,6 +22,7 @@ import useErrorToast from "@/hooks/useErrorToast" import { validate as validateUUID, v4 as uuidv4 } from "uuid" import useDesktopConfig from "@/hooks/useDesktopConfig" import { type SyncMode } from "@filen/sync/dist/types" +import { Switch } from "../ui/switch" export const CreateSyncDialog = memo(() => { const [open, setOpen] = useState(false) @@ -32,12 +33,16 @@ export const CreateSyncDialog = memo(() => { remotePath: string remoteUUID: string mode: SyncMode + paused: boolean + excludeDotFiles: boolean }>({ name: "", localPath: "", remotePath: "", remoteUUID: "", - mode: "twoWay" + mode: "twoWay", + paused: true, + excludeDotFiles: true }) const errorToast = useErrorToast() const [, setDesktopConfig] = useDesktopConfig() @@ -96,8 +101,8 @@ export const CreateSyncDialog = memo(() => { localPath: createState.localPath, name: createState.name, mode: createState.mode, - excludeDotFiles: true, - paused: false + excludeDotFiles: createState.excludeDotFiles, + paused: createState.paused } setDesktopConfig(prev => ({ @@ -113,7 +118,9 @@ export const CreateSyncDialog = memo(() => { localPath: "", remotePath: "", remoteUUID: "", - mode: "twoWay" + mode: "twoWay", + paused: true, + excludeDotFiles: true }) close() @@ -185,6 +192,20 @@ export const CreateSyncDialog = memo(() => { } }, [errorToast, t]) + const onPausedChange = useCallback((paused: boolean) => { + setCreateState(prev => ({ + ...prev, + paused + })) + }, []) + + const onExcludeDotFilesChange = useCallback((excludeDotFiles: boolean) => { + setCreateState(prev => ({ + ...prev, + excludeDotFiles + })) + }, []) + useEffect(() => { const listener = eventEmitter.on("openCreateSyncDialog", () => { setOpen(true) @@ -268,7 +289,7 @@ export const CreateSyncDialog = memo(() => { - +
@@ -306,6 +327,24 @@ export const CreateSyncDialog = memo(() => {
+
+

{t("dialogs.createSync.paused")}

+
+ +
+
+
+

{t("dialogs.createSync.excludeDotFiles")}

+
+ +
+
diff --git a/src/components/dialogs/infoDialog/content.tsx b/src/components/dialogs/infoDialog/content.tsx index dde40e46..7e383358 100644 --- a/src/components/dialogs/infoDialog/content.tsx +++ b/src/components/dialogs/infoDialog/content.tsx @@ -14,6 +14,8 @@ import useSDKConfig from "@/hooks/useSDKConfig" import { validate as validateUUID } from "uuid" import pathModule from "path" import { type DirectorySizeResult } from "@/lib/worker/worker" +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip" +import { TOOLTIP_POPUP_DELAY } from "@/constants" export const Content = memo(({ item }: { item: DriveCloudItem }) => { const [size, setSize] = useState(item.size) @@ -89,7 +91,7 @@ export const Content = memo(({ item }: { item: DriveCloudItem }) => { return (
-
+
{item.type === "directory" ? ( {
-
+

{t("dialogs.info.name")}

{item.name}

-
+

{t("dialogs.info.size")}

{formatBytes(size)}

{item.type === "directory" && (
-
+

{t("dialogs.info.directories")}

{dirSize.folders}

-
+

{t("dialogs.info.files")}

{dirSize.files}

)}
-
+

{t("dialogs.info.type")}

{item.type === "file" ? item.mime : t("dialogs.info.directoryType")}

-
+

{t("dialogs.info.location")}

-

{itemLocation}

+ + + +

{itemLocation}

+
+ +

{itemLocation}

+
+
+
-
+

{t("dialogs.info.modified")}

{simpleDate(item.lastModified)}

-
+

{t("dialogs.info.uploaded")}

{simpleDate(item.timestamp)}

diff --git a/src/components/dialogs/noteHistory/content.tsx b/src/components/dialogs/noteHistory/content.tsx index f1693b53..b5dca61b 100644 --- a/src/components/dialogs/noteHistory/content.tsx +++ b/src/components/dialogs/noteHistory/content.tsx @@ -45,7 +45,7 @@ export const Content = memo(({ note, setOpen }: { note: Note; setOpen: React.Dis const itemContent = useCallback( (_: number, history: NoteHistory) => { return ( -
+
-
+
{selectedHistory && ( <> - {selectedHistory.type === "rich" || selectedHistory.type === "checklist" ? ( - - ) : ( - - )} +
+ {selectedHistory.type === "rich" || selectedHistory.type === "checklist" ? ( + + ) : ( + + )} +
- )} - {canGoToNextItem && ( - - )} - - )} - {(previewType === "text" || previewType === "code" || previewType === "md") && ( - <> - {buffers[item.uuid] ? ( - - ) : ( - - )} - - )} - {previewType === "docx" && <>{buffers[item.uuid] ? : }} - {previewType === "pdf" && <>{urlObjects[item.uuid] ? : }} - {previewType === "image" && ( - - )} - {previewType === "video" && ( - <>{urlObjects[item.uuid] ?
)} diff --git a/src/components/dialogs/publicLink/directory.tsx b/src/components/dialogs/publicLink/directory.tsx index 1fb76f26..16d1b9ba 100644 --- a/src/components/dialogs/publicLink/directory.tsx +++ b/src/components/dialogs/publicLink/directory.tsx @@ -265,7 +265,7 @@ export const Directory = memo( />
-

{t("dialogs.publicLink.downloadButton")}

+

{t("dialogs.publicLink.downloadButton")}

-

{t("dialogs.publicLink.downloadButton")}

+

{t("dialogs.publicLink.downloadButton")}

{ try { await promiseAllChunked(promises) } catch (e) { - console.error(e) + if (e instanceof Error && !e.message.toLowerCase().includes("abort")) { + console.error(e) - errorToast((e as unknown as Error).message ?? (e as unknown as Error).toString()) + errorToast((e as unknown as Error).message ?? (e as unknown as Error).toString()) + } } finally { e.target.value = "" } @@ -122,9 +124,11 @@ export const Drive = memo(() => { ]) } } catch (e) { - console.error(e) + if (e instanceof Error && !e.message.toLowerCase().includes("abort")) { + console.error(e) - errorToast((e as unknown as Error).message ?? (e as unknown as Error).toString()) + errorToast((e as unknown as Error).message ?? (e as unknown as Error).toString()) + } } finally { e.target.value = "" } diff --git a/src/components/drive/list/item/contextMenu/index.tsx b/src/components/drive/list/item/contextMenu/index.tsx index 4f10f6de..25fe494a 100644 --- a/src/components/drive/list/item/contextMenu/index.tsx +++ b/src/components/drive/list/item/contextMenu/index.tsx @@ -184,7 +184,11 @@ export const ContextMenu = memo( linkSalt: publicLinkPaswordState.salt.length > 0 ? publicLinkPaswordState.salt : undefined }) } catch (e) { - console.error(e) + if (e instanceof Error && !e.message.toLowerCase().includes("abort")) { + console.error(e) + + errorToast((e as unknown as Error).message ?? (e as unknown as Error).toString()) + } } }, [ selectedItems, @@ -192,7 +196,8 @@ export const ContextMenu = memo( publicLinkURLState.isPublicLink, publicLinkPaswordState.password, publicLinkURLState.uuid, - publicLinkPaswordState.salt + publicLinkPaswordState.salt, + errorToast ]) const trash = useCallback(async () => { diff --git a/src/components/dropZone/index.tsx b/src/components/dropZone/index.tsx index abd05333..0ce2a5f6 100644 --- a/src/components/dropZone/index.tsx +++ b/src/components/dropZone/index.tsx @@ -231,9 +231,11 @@ export const DropZone = memo(({ children }: { children: React.ReactNode }) => { } } } catch (e) { - console.error(e) + if (e instanceof Error && !e.message.toLowerCase().includes("abort")) { + console.error(e) - errorToast((e as unknown as Error).message ?? (e as unknown as Error).toString()) + errorToast((e as unknown as Error).message ?? (e as unknown as Error).toString()) + } } finally { if (toast) { toast.dismiss() diff --git a/src/components/mainContainer/index.tsx b/src/components/mainContainer/index.tsx index 1ad06b2b..d17a1a9f 100644 --- a/src/components/mainContainer/index.tsx +++ b/src/components/mainContainer/index.tsx @@ -17,13 +17,34 @@ import useIsMobile from "@/hooks/useIsMobile" export const Wrapper = memo(({ children }: { children: React.ReactNode }) => { const { t } = useTranslation() const { dark } = useTheme() + const [closeToTrayEnabled] = useLocalStorage("closeToTrayEnabled", false) - const minimizeWindow = useCallback(() => { - window.desktopAPI.minimizeWindow().catch(console.error) - }, []) + const minimizeWindow = useCallback(async () => { + try { + if (closeToTrayEnabled) { + await window.desktopAPI.hideWindow() + + return + } - const maximizeWindow = useCallback(() => { - window.desktopAPI.maximizeWindow().catch(console.error) + await window.desktopAPI.minimizeWindow() + } catch (e) { + console.error(e) + } + }, [closeToTrayEnabled]) + + const maximizeWindow = useCallback(async () => { + try { + if (await window.desktopAPI.isWindowMaximized()) { + await window.desktopAPI.unmaximizeWindow() + + return + } + + await window.desktopAPI.maximizeWindow() + } catch (e) { + console.error(e) + } }, []) const closeWindow = useCallback( @@ -63,9 +84,16 @@ export const Wrapper = memo(({ children }: { children: React.ReactNode }) => { width: SIDEBAR_WIDTH - 2 }} > -

Filen

+

+ Filen +

-
+
{!location.includes("/terminal") && ( { return desktopConfig.syncConfig.syncPairs.filter(pair => pair.uuid === sync.uuid)[0] ?? null }, [sync.uuid, desktopConfig]) - const togglePause = useCallback(() => { + const togglePause = useCallback(async () => { if (!syncConfig) { return } - setDesktopConfig(prev => ({ - ...prev, - syncConfig: { - ...prev.syncConfig, - syncPairs: prev.syncConfig.syncPairs.map(pair => (pair.uuid === sync.uuid ? { ...pair, paused: !syncConfig.paused } : pair)) - } - })) - }, [syncConfig, sync.uuid, setDesktopConfig]) + setChanging(true) + + const toast = loadingToast() + + try { + const paused = !syncConfig.paused + + await window.desktopAPI.syncUpdatePaused({ + uuid: sync.uuid, + paused + }) + + await window.desktopAPI.syncResetCache({ + uuid: sync.uuid + }) + + setDesktopConfig(prev => ({ + ...prev, + syncConfig: { + ...prev.syncConfig, + syncPairs: prev.syncConfig.syncPairs.map(pair => (pair.uuid === sync.uuid ? { ...pair, paused } : pair)) + } + })) + } catch (e) { + console.error(e) + + errorToast((e as unknown as Error).message ?? (e as unknown as Error).toString()) + } finally { + setChanging(false) + + toast.dismiss() + } + }, [syncConfig, sync.uuid, setDesktopConfig, setChanging, errorToast, loadingToast]) const deleteSync = useCallback(async () => { if ( @@ -53,21 +82,40 @@ export const ContextMenu = memo(({ sync, children }: { sync: SyncPair; children: return } - setSelectedSync(null) - setDesktopConfig(prev => ({ - ...prev, - syncConfig: { - ...prev.syncConfig, - syncPairs: prev.syncConfig.syncPairs.filter(pair => pair.uuid !== sync.uuid) - } - })) + setChanging(true) - navigate({ - to: "/syncs", - replace: true, - resetScroll: true - }) - }, [setDesktopConfig, sync.uuid, navigate, t, setSelectedSync]) + const toast = loadingToast() + + try { + await window.desktopAPI.syncUpdateRemoved({ + uuid: sync.uuid, + removed: true + }) + + setSelectedSync(null) + setDesktopConfig(prev => ({ + ...prev, + syncConfig: { + ...prev.syncConfig, + syncPairs: prev.syncConfig.syncPairs.filter(pair => pair.uuid !== sync.uuid) + } + })) + + navigate({ + to: "/syncs", + replace: true, + resetScroll: true + }) + } catch (e) { + console.error(e) + + errorToast((e as unknown as Error).message ?? (e as unknown as Error).toString()) + } finally { + setChanging(false) + + toast.dismiss() + } + }, [setDesktopConfig, sync.uuid, navigate, t, setSelectedSync, setChanging, errorToast, loadingToast]) useEffect(() => { const deleteSyncListener = eventEmitter.on("deleteSync", (uuid: string) => { diff --git a/src/components/mainContainer/innerSideBar/syncs/sync/index.tsx b/src/components/mainContainer/innerSideBar/syncs/sync/index.tsx index 3f8ffb25..8cc92ad0 100644 --- a/src/components/mainContainer/innerSideBar/syncs/sync/index.tsx +++ b/src/components/mainContainer/innerSideBar/syncs/sync/index.tsx @@ -1,13 +1,15 @@ import { memo } from "react" import { type SyncPair } from "@filen/sync/dist/types" -import { RefreshCcw } from "lucide-react" +import { RefreshCw } from "lucide-react" import { Link } from "@tanstack/react-router" import useRouteParent from "@/hooks/useRouteParent" import { cn } from "@/lib/utils" import ContextMenu from "./contextMenu" +import useIsSyncActive from "@/hooks/useIsSyncActive" export const Sync = memo(({ sync }: { sync: SyncPair }) => { const routeParent = useRouteParent() + const isSyncActive = useIsSyncActive(sync.uuid) return (
@@ -23,7 +25,10 @@ export const Sync = memo(({ sync }: { sync: SyncPair }) => { routeParent === sync.uuid ? "bg-secondary" : "bg-transparent" )} > - +

{sync.name}

diff --git a/src/components/mainContainer/sideBar/button.tsx b/src/components/mainContainer/sideBar/button.tsx index 942eb33e..bc8a706a 100644 --- a/src/components/mainContainer/sideBar/button.tsx +++ b/src/components/mainContainer/sideBar/button.tsx @@ -1,5 +1,5 @@ import { memo, useCallback, useMemo } from "react" -import { RefreshCcw, HardDrive, Notebook, MessageCircle, Contact, ArrowDownUp, Settings, MessageCircleMore, Terminal } from "lucide-react" +import { HardDrive, Notebook, MessageCircle, Contact, ArrowDownUp, Settings, MessageCircleMore, Terminal } from "lucide-react" import { Link } from "@tanstack/react-router" import useSDKConfig from "@/hooks/useSDKConfig" import useRouteParent from "@/hooks/useRouteParent" @@ -15,6 +15,7 @@ import useLocation from "@/hooks/useLocation" import { useChatsStore } from "@/stores/chats.store" import { useContactsStore } from "@/stores/contacts.store" import LogoSVG from "@/assets/logo" +import SyncIndicator from "./syncIndicator" const iconSize = IS_DESKTOP && IS_APPLE_DEVICE ? 26 : 24 @@ -124,7 +125,7 @@ export const Button = memo(({ id }: { id: string }) => { params={link.params} draggable={false} > - {id === "syncs" && } + {id === "syncs" && } {id === "mounts" && } {id === baseFolderUUID && (
{ return (
{ + const isSyncActive = useIsSyncActive() + + return ( + + ) +}) + +export default SyncIndicator diff --git a/src/components/mainContainer/topBar/breadcrumbs/item.tsx b/src/components/mainContainer/topBar/breadcrumbs/item.tsx index cebd2d23..e41564f7 100644 --- a/src/components/mainContainer/topBar/breadcrumbs/item.tsx +++ b/src/components/mainContainer/topBar/breadcrumbs/item.tsx @@ -5,12 +5,15 @@ import { getItem } from "@/lib/localForage" import { useNavigate } from "@tanstack/react-router" import { useTranslation } from "react-i18next" import { directoryUUIDToNameCache } from "@/cache" +import useRouteParent from "@/hooks/useRouteParent" +import { cn } from "@/lib/utils" export const Item = memo(({ path, index, pathname }: { path: string; index: number; pathname: string }) => { const { baseFolderUUID } = useSDKConfig() const navigate = useNavigate() const { t } = useTranslation() const [name, setName] = useState(directoryUUIDToNameCache.has(path) ? (directoryUUIDToNameCache.get(path) as string) : "") + const routeParent = useRouteParent() const navigateToPath = useCallback(() => { let builtPathname = "" @@ -107,12 +110,20 @@ export const Item = memo(({ path, index, pathname }: { path: string; index: numb }} >

{path === baseFolderUUID ? t("topBar.breadcrumb.cloudDrive") : name}

- {index < pathname.split("/").length - 1 && } + {index < pathname.split("/").length - 1 && ( + + )}
) }) diff --git a/src/components/mainContainer/topBar/index.tsx b/src/components/mainContainer/topBar/index.tsx index 89d5f2c4..560f4faf 100644 --- a/src/components/mainContainer/topBar/index.tsx +++ b/src/components/mainContainer/topBar/index.tsx @@ -80,8 +80,6 @@ export const TopBar = memo(() => { await worker.emptyTrash() setItems([]) - - eventEmitter.emit("refetchDrive") } catch (e) { console.error(e) diff --git a/src/components/mounts/s3/index.tsx b/src/components/mounts/s3/index.tsx index 1ea5e54c..64340cd8 100644 --- a/src/components/mounts/s3/index.tsx +++ b/src/components/mounts/s3/index.tsx @@ -16,6 +16,7 @@ import { isPortValidLocally, isValidIPv4 } from "../utils" import eventEmitter from "@/lib/eventEmitter" import { Button } from "@/components/ui/button" import useSuccessToast from "@/hooks/useSuccessToast" +import { useMountsStore } from "@/stores/mounts.store" export async function isS3Online(): Promise<{ online: boolean }> { const [online, active] = await Promise.all([window.desktopAPI.isS3Online(), window.desktopAPI.isS3Active()]) @@ -27,7 +28,6 @@ export async function isS3Online(): Promise<{ online: boolean }> { export const S3 = memo(() => { const settingsContainerSize = useSettingsContainerSize() - const [enabling, setEnabling] = useState(false) const [desktopConfig, setDesktopConfig] = useDesktopConfig() const { t } = useTranslation() const errorToast = useErrorToast() @@ -36,6 +36,7 @@ export const S3 = memo(() => { const [accessKeyId, setAccessKeyId] = useState(desktopConfig.s3Config.accessKeyId) const [secretKeyId, setSecretKeyId] = useState(desktopConfig.s3Config.accessKeyId) const successToast = useSuccessToast() + const { enablingS3, setEnablingS3 } = useMountsStore() const isOnlineQuery = useQuery({ queryKey: ["isS3Online"], @@ -44,7 +45,7 @@ export const S3 = memo(() => { const onCheckedChange = useCallback( async (checked: boolean) => { - if (enabling) { + if (enablingS3) { return } @@ -72,7 +73,7 @@ export const S3 = memo(() => { return } - setEnabling(true) + setEnablingS3(true) try { if (checked) { @@ -115,15 +116,15 @@ export const S3 = memo(() => { } })) } finally { - setEnabling(false) + setEnablingS3(false) } }, - [t, enabling, setDesktopConfig, isOnlineQuery, errorToast, accessKeyId, secretKeyId, port, hostname] + [t, setEnablingS3, enablingS3, setDesktopConfig, isOnlineQuery, errorToast, accessKeyId, secretKeyId, port, hostname] ) const onProtocolChange = useCallback( async (protocol: "http" | "https") => { - if (enabling) { + if (enablingS3) { return } @@ -139,7 +140,7 @@ export const S3 = memo(() => { return } - setEnabling(true) + setEnablingS3(true) try { if ((await isS3Online()).online) { @@ -180,10 +181,10 @@ export const S3 = memo(() => { } })) } finally { - setEnabling(false) + setEnablingS3(false) } }, - [errorToast, enabling, isOnlineQuery, setDesktopConfig, accessKeyId, secretKeyId, port, t, hostname] + [errorToast, setEnablingS3, enablingS3, isOnlineQuery, setDesktopConfig, accessKeyId, secretKeyId, port, t, hostname] ) const copyConnect = useCallback(async () => { @@ -230,7 +231,7 @@ export const S3 = memo(() => { WebkitAppRegion: "drag" }} > - {enabling ? ( + {enablingS3 ? ( ) : isOnlineQuery.data.online ? (
@@ -246,7 +247,7 @@ export const S3 = memo(() => { className="mt-10" > @@ -258,7 +259,7 @@ export const S3 = memo(() => { @@ -480,7 +509,7 @@ export const VirtualDrive = memo(() => { @@ -504,7 +533,7 @@ export const VirtualDrive = memo(() => { onClick={cleanupCache} size="sm" variant="destructive" - disabled={enabling || (isMountedQuery.isSuccess && isMountedQuery.data.mounted)} + disabled={enablingVirtualDrive || (isMountedQuery.isSuccess && isMountedQuery.data.mounted)} > {t("mounts.virtualDrive.clear")} @@ -521,7 +550,7 @@ export const VirtualDrive = memo(() => { - {!enabling && isMountedQuery.data.mounted && ( + {!enablingVirtualDrive && isMountedQuery.data.mounted && (
{ const [online, active] = await Promise.all([window.desktopAPI.isWebDAVOnline(), window.desktopAPI.isWebDAVActive()]) @@ -27,7 +28,7 @@ export async function isWebDAVOnline(): Promise<{ online: boolean }> { export const WebDAV = memo(() => { const settingsContainerSize = useSettingsContainerSize() - const [enabling, setEnabling] = useState(false) + const { enablingWebDAV, setEnablingWebDAV } = useMountsStore() const [desktopConfig, setDesktopConfig] = useDesktopConfig() const { t } = useTranslation() const errorToast = useErrorToast() @@ -44,7 +45,7 @@ export const WebDAV = memo(() => { const onCheckedChange = useCallback( async (checked: boolean) => { - if (enabling) { + if (enablingWebDAV) { return } @@ -72,7 +73,7 @@ export const WebDAV = memo(() => { return } - setEnabling(true) + setEnablingWebDAV(true) try { if (checked) { @@ -115,15 +116,15 @@ export const WebDAV = memo(() => { } })) } finally { - setEnabling(false) + setEnablingWebDAV(false) } }, - [t, enabling, isOnlineQuery, errorToast, setDesktopConfig, username, password, port, hostname] + [t, enablingWebDAV, setEnablingWebDAV, isOnlineQuery, errorToast, setDesktopConfig, username, password, port, hostname] ) const onAuthModeChange = useCallback( async (mode: "basic" | "digest") => { - if (enabling) { + if (enablingWebDAV) { return } @@ -139,7 +140,7 @@ export const WebDAV = memo(() => { return } - setEnabling(true) + setEnablingWebDAV(true) try { if ((await isWebDAVOnline()).online) { @@ -180,15 +181,15 @@ export const WebDAV = memo(() => { } })) } finally { - setEnabling(false) + setEnablingWebDAV(false) } }, - [errorToast, enabling, isOnlineQuery, setDesktopConfig, port, username, password, t, hostname] + [errorToast, enablingWebDAV, setEnablingWebDAV, isOnlineQuery, setDesktopConfig, port, username, password, t, hostname] ) const onProtocolChange = useCallback( async (protocol: "http" | "https") => { - if (enabling) { + if (enablingWebDAV) { return } @@ -204,7 +205,7 @@ export const WebDAV = memo(() => { return } - setEnabling(true) + setEnablingWebDAV(true) try { if ((await isWebDAVOnline()).online) { @@ -245,15 +246,15 @@ export const WebDAV = memo(() => { } })) } finally { - setEnabling(false) + setEnablingWebDAV(false) } }, - [errorToast, enabling, isOnlineQuery, setDesktopConfig, username, password, port, t, hostname] + [errorToast, enablingWebDAV, setEnablingWebDAV, isOnlineQuery, setDesktopConfig, username, password, port, t, hostname] ) const onProxyModeChange = useCallback( async (checked: boolean) => { - if (enabling) { + if (enablingWebDAV) { return } @@ -269,7 +270,7 @@ export const WebDAV = memo(() => { return } - setEnabling(true) + setEnablingWebDAV(true) try { if ((await isWebDAVOnline()).online) { @@ -310,10 +311,10 @@ export const WebDAV = memo(() => { } })) } finally { - setEnabling(false) + setEnablingWebDAV(false) } }, - [errorToast, enabling, isOnlineQuery, setDesktopConfig, username, password, port, t, hostname] + [errorToast, enablingWebDAV, setEnablingWebDAV, isOnlineQuery, setDesktopConfig, username, password, port, t, hostname] ) const copyConnect = useCallback(async () => { @@ -367,7 +368,7 @@ export const WebDAV = memo(() => { WebkitAppRegion: "drag" }} > - {enabling ? ( + {enablingWebDAV ? ( ) : isOnlineQuery.data.online ? (
@@ -383,7 +384,7 @@ export const WebDAV = memo(() => { className="mt-10" > @@ -395,7 +396,7 @@ export const WebDAV = memo(() => { @@ -467,7 +468,7 @@ export const WebDAV = memo(() => { setUsername(user.length === 0 ? "admin" : user) }} className="w-[200px]" - disabled={enabling || (isOnlineQuery.isSuccess && isOnlineQuery.data.online)} + disabled={enablingWebDAV || (isOnlineQuery.isSuccess && isOnlineQuery.data.online)} autoCapitalize="none" autoComplete="none" autoCorrect="none" @@ -488,7 +489,7 @@ export const WebDAV = memo(() => { setPassword(pass.length === 0 ? "admin" : pass) }} className="w-[200px]" - disabled={enabling || (isOnlineQuery.isSuccess && isOnlineQuery.data.online)} + disabled={enablingWebDAV || (isOnlineQuery.isSuccess && isOnlineQuery.data.online)} autoCapitalize="none" autoComplete="none" autoCorrect="none" @@ -501,12 +502,12 @@ export const WebDAV = memo(() => { info={t("mounts.webdav.sections.proxyMode.info")} >
- {!enabling && isOnlineQuery.isSuccess && isOnlineQuery.data.online && ( + {!enablingWebDAV && isOnlineQuery.isSuccess && isOnlineQuery.data.online && (
{ useEffect(() => { if (IS_DESKTOP) { - return - } + window.desktopAPI.updateNotificationCount(unread + requestsInCount).catch(console.error) + } else { + const link = document.querySelector("link[rel~='icon']") as HTMLLinkElement - const link = document.querySelector("link[rel~='icon']") as HTMLLinkElement + if (!link) { + return + } - if (!link) { - return - } + const oldHref = link.href + const newHref = unread + requestsInCount > 0 ? "./notification-favicon.ico" : "./favicon.ico" - const oldHref = link.href - const newHref = unread + requestsInCount > 0 ? "./notification-favicon.ico" : "./favicon.ico" + if (newHref === oldHref) { + return + } - if (newHref === oldHref) { - return + link.href = newHref } - - link.href = newHref }, [unread, requestsInCount]) useEffect(() => { diff --git a/src/components/publicLink/directory/breadcrumbs/item.tsx b/src/components/publicLink/directory/breadcrumbs/item.tsx index 74e6f2c6..81fb2db3 100644 --- a/src/components/publicLink/directory/breadcrumbs/item.tsx +++ b/src/components/publicLink/directory/breadcrumbs/item.tsx @@ -25,7 +25,7 @@ export const Item = memo(({ uuid, info, ex, index }: { info: DirLinkInfoDecrypte return (
  • {uuid === info.parent diff --git a/src/components/publicLink/directory/directory.tsx b/src/components/publicLink/directory/directory.tsx index e3883b5b..356d607f 100644 --- a/src/components/publicLink/directory/directory.tsx +++ b/src/components/publicLink/directory/directory.tsx @@ -86,9 +86,9 @@ export const Directory = memo(({ info, password }: { info: DirLinkInfoDecryptedR linkSalt: info.hasPassword ? info.salt : undefined }) } catch (e) { - console.error(e) + if (e instanceof Error && !e.message.toLowerCase().includes("abort")) { + console.error(e) - if (!(e as unknown as Error).toString().includes("abort")) { errorToast((e as unknown as Error).message ?? (e as unknown as Error).toString()) } } diff --git a/src/components/settings/account/index.tsx b/src/components/settings/account/index.tsx index cad55959..e8135721 100644 --- a/src/components/settings/account/index.tsx +++ b/src/components/settings/account/index.tsx @@ -292,9 +292,11 @@ export const Account = memo(() => { await worker.uploadAvatar({ buffer: transfer(buffer, [buffer.buffer]) }) await account.refetch() } catch (e) { - console.error(e) + if (e instanceof Error && !e.message.toLowerCase().includes("abort")) { + console.error(e) - errorToast((e as unknown as Error).message ?? (e as unknown as Error).toString()) + errorToast((e as unknown as Error).message ?? (e as unknown as Error).toString()) + } } finally { toast.dismiss() diff --git a/src/components/settings/general/index.tsx b/src/components/settings/general/index.tsx index 6402b817..7c0fc346 100644 --- a/src/components/settings/general/index.tsx +++ b/src/components/settings/general/index.tsx @@ -31,6 +31,8 @@ export const General = memo(() => { const [contactNotificationsEnabled, setContactNotificationsEnabled] = useLocalStorage("contactNotificationsEnabled", false) const [defaultNoteType, setDefaultNoteType] = useLocalStorage("defaultNoteType", "text") const settingsContainerSize = useSettingsContainerSize() + const [autoLaunchEnabled, setAutoLaunchEnabled] = useLocalStorage("autoLaunchEnabled", false) + const [closeToTrayEnabled, setCloseToTrayEnabled] = useLocalStorage("closeToTrayEnabled", false) const thumbnailCacheQuery = useQuery({ queryKey: ["workerCalculateThumbnailCacheUsage"], @@ -195,6 +197,25 @@ export const General = memo(() => { [loadingToast, errorToast, thumbnailCacheQuery, t] ) + const toggleAutoLaunch = useCallback( + async (enabled: boolean) => { + const toast = loadingToast() + + try { + await window.desktopAPI.toggleAutoLaunch(enabled) + + setAutoLaunchEnabled(enabled) + } catch (e) { + console.error(e) + + errorToast((e as unknown as Error).message ?? (e as unknown as Error).toString()) + } finally { + toast.dismiss() + } + }, + [loadingToast, errorToast, setAutoLaunchEnabled] + ) + if (!account) { return } @@ -273,9 +294,33 @@ export const General = memo(() => {

  • {IS_DESKTOP && ( <> + {window.desktopAPI.platform() !== "linux" && ( + <> +
    + +
    +
    + +
    + + )}
    { + const { t } = useTranslation() + const itemName = useMemo(() => { return pathModule.posix.basename(event.relativePath) }, [event.relativePath]) return (
    -
    - {event.of === "createLocalDirectory" || - event.of === "renameLocalDirectory" || - event.of === "deleteLocalDirectory" || - event.of === "createRemoteDirectory" || - event.of === "renameRemoteDirectory" || - event.of === "deleteRemoteDirectory" ? ( - - ) : ( - - )} -

    - {event.of} {itemName} -

    +
    + + + +
    + {event.of === "createLocalDirectory" || + event.of === "renameLocalDirectory" || + event.of === "deleteLocalDirectory" || + event.of === "createRemoteDirectory" || + event.of === "renameRemoteDirectory" || + event.of === "deleteRemoteDirectory" ? ( + + ) : ( + + )} +

    + {event.of === "createLocalDirectory" + ? t("syncs.events.createLocalDirectory", { name: itemName }) + : event.of === "createRemoteDirectory" + ? t("syncs.events.createRemoteDirectory", { name: itemName }) + : event.of === "deleteLocalDirectory" + ? t("syncs.events.deleteLocalDirectory", { name: itemName }) + : event.of === "deleteLocalFile" + ? t("syncs.events.deleteLocalFile", { name: itemName }) + : event.of === "deleteRemoteDirectory" + ? t("syncs.events.deleteRemoteDirectory", { name: itemName }) + : event.of === "deleteRemoteFile" + ? t("syncs.events.deleteRemoteFile", { name: itemName }) + : event.of === "download" + ? t("syncs.events.download", { name: itemName }) + : event.of === "upload" + ? t("syncs.events.upload", { name: itemName }) + : event.of === "downloadFile" + ? t("syncs.events.download", { name: itemName }) + : event.of === "uploadFile" + ? t("syncs.events.upload", { name: itemName }) + : event.of === "renameLocalDirectory" + ? t("syncs.events.renameLocalDirectory", { name: itemName }) + : event.of === "renameLocalFile" + ? t("syncs.events.renameLocalFile", { name: itemName }) + : event.of === "renameRemoteDirectory" + ? t("syncs.events.renameRemoteDirectory", { + name: itemName + }) + : event.of === "renameRemoteFile" + ? t("syncs.events.renameRemoteFile", { + name: itemName + }) + : ""} +

    +
    +
    + +

    + {event.of === "createLocalDirectory" || + event.of === "deleteLocalDirectory" || + event.of === "deleteLocalFile" || + event.of === "renameLocalDirectory" || + event.of === "renameLocalFile" || + event.of === "download" || + event.of === "downloadFile" + ? event.localPath + : event.relativePath} +

    +
    +
    +
    ) diff --git a/src/components/syncs/content/events/index.tsx b/src/components/syncs/content/events/index.tsx index 6614f753..4531bc78 100644 --- a/src/components/syncs/content/events/index.tsx +++ b/src/components/syncs/content/events/index.tsx @@ -15,7 +15,7 @@ export const Events = memo(({ sync }: { sync: SyncPair }) => { const { t } = useTranslation() const virtuosoHeight = useMemo(() => { - return windowSize.height - 64 - DESKTOP_TOPBAR_HEIGHT + return windowSize.height - 64 - 13 - DESKTOP_TOPBAR_HEIGHT }, [windowSize.height]) const state = useMemo(() => { @@ -56,6 +56,7 @@ export const Events = memo(({ sync }: { sync: SyncPair }) => { computeItemKey={getItemKey} itemContent={itemContent} components={components} + defaultItemHeight={51} style={{ overflowX: "hidden", overflowY: "auto", diff --git a/src/components/syncs/content/ignored/ignore.tsx b/src/components/syncs/content/ignored/ignore.tsx index f4089dbc..71a4c088 100644 --- a/src/components/syncs/content/ignored/ignore.tsx +++ b/src/components/syncs/content/ignored/ignore.tsx @@ -1,13 +1,46 @@ -import { memo } from "react" +import { memo, useMemo } from "react" import { type IgnoreType } from "./index" +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip" +import { TOOLTIP_POPUP_DELAY } from "@/constants" +import { useTranslation } from "react-i18next" +import { fileNameToSVGIcon } from "@/assets/fileExtensionIcons" +import pathModule from "path" export const Ignore = memo(({ ignore }: { ignore: IgnoreType }) => { + const { t } = useTranslation() + + const itemName = useMemo(() => { + return pathModule.posix.basename(ignore.relativePath) + }, [ignore.relativePath]) + return (
    -
    -

    - {ignore.localPath} {ignore.reason} -

    +
    + + + +
    + +

    {ignore.localPath}

    +
    +
    + +

    + {ignore.reason === "dotFile" + ? t("syncs.ignored.reasons.dotFile") + : ignore.reason === "defaultIgnore" + ? t("syncs.ignored.reasons.defaultIgnore") + : ignore.reason === "empty" + ? t("syncs.ignored.reasons.empty") + : t("syncs.ignored.reasons.defaultIgnore")} +

    +
    +
    +
    ) diff --git a/src/components/syncs/content/ignored/index.tsx b/src/components/syncs/content/ignored/index.tsx index f32e0d6a..45e1a067 100644 --- a/src/components/syncs/content/ignored/index.tsx +++ b/src/components/syncs/content/ignored/index.tsx @@ -23,7 +23,7 @@ export const Ignored = memo(({ sync }: { sync: SyncPair }) => { const { t } = useTranslation() const virtuosoHeight = useMemo(() => { - return windowSize.height - 64 - DESKTOP_TOPBAR_HEIGHT + return windowSize.height - 64 - 12 - DESKTOP_TOPBAR_HEIGHT }, [windowSize.height]) const ignored = useMemo(() => { @@ -84,6 +84,7 @@ export const Ignored = memo(({ sync }: { sync: SyncPair }) => { computeItemKey={getItemKey} itemContent={itemContent} components={components} + defaultItemHeight={51} style={{ overflowX: "hidden", overflowY: "auto", diff --git a/src/components/syncs/content/index.tsx b/src/components/syncs/content/index.tsx index d4b6a8d4..0c19d449 100644 --- a/src/components/syncs/content/index.tsx +++ b/src/components/syncs/content/index.tsx @@ -1,17 +1,27 @@ -import { memo, useMemo } from "react" +import { memo, useMemo, useState, useCallback } from "react" import { type SyncPair } from "@filen/sync/dist/types" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { useSyncsStore } from "@/stores/syncs.store" -import { AlertCircle } from "lucide-react" +import { AlertCircle, RefreshCw, Pause, Play } from "lucide-react" import Events from "./events" import Ignored from "./ignored" import Settings from "./settings" import { useTranslation } from "react-i18next" import Issues from "./issues" +import Transfers from "./transfers" +import { Button } from "@/components/ui/button" +import eventEmitter from "@/lib/eventEmitter" +import useDesktopConfig from "@/hooks/useDesktopConfig" export const Content = memo(({ sync }: { sync: SyncPair }) => { const { cycleState, transfers, localIgnored, remoteIgnored, errors } = useSyncsStore() const { t } = useTranslation() + const [activeTab, setActiveTab] = useState("events") + const [desktopConfig] = useDesktopConfig() + + const syncConfig = useMemo(() => { + return desktopConfig.syncConfig.syncPairs.filter(pair => pair.uuid === sync.uuid)[0] ?? null + }, [sync.uuid, desktopConfig]) const state = useMemo(() => { return { @@ -19,15 +29,25 @@ export const Content = memo(({ sync }: { sync: SyncPair }) => { cycleState: cycleState[sync.uuid] ? cycleState[sync.uuid]! : "Starting", localIgnored: localIgnored[sync.uuid] ? localIgnored[sync.uuid]! : [], remoteIgnored: remoteIgnored[sync.uuid] ? remoteIgnored[sync.uuid]! : [], - errors: errors[sync.uuid] ? errors[sync.uuid]! : [] + errors: errors[sync.uuid] ? errors[sync.uuid]! : [], + ongoingTransfers: transfers[sync.uuid] + ? transfers[sync.uuid]!.filter( + transfer => transfer.state === "queued" || transfer.state === "started" || transfer.state === "paused" + ) + : [] } }, [sync.uuid, cycleState, transfers, localIgnored, remoteIgnored, errors]) + const togglePause = useCallback(() => { + eventEmitter.emit("toggleSyncPause", sync.uuid) + }, [sync.uuid]) + return (
    { WebkitAppRegion: "no-drag" }} > - {t("syncs.events")} + {t("syncs.eventsTitle")} + + {state.ongoingTransfers.length > 0 && ( + + )} +

    {t("syncs.transfersTitle")}

    +
    {state.localIgnored.length + state.remoteIgnored.length > 0 && ( - + )} -

    {t("syncs.ignored")}

    +

    {t("syncs.ignoredTitle")}

    {state.errors.length > 0 && } -

    {t("syncs.issues")}

    +

    {t("syncs.issuesTitle")}

    {t("syncs.settingsTitle")} -
    {state.cycleState}
    + {activeTab === "transfers" && syncConfig && state.ongoingTransfers.length > 0 && ( +
    + +
    + )}
    + + + diff --git a/src/components/syncs/content/issues/index.tsx b/src/components/syncs/content/issues/index.tsx index 3705eb8d..415132d1 100644 --- a/src/components/syncs/content/issues/index.tsx +++ b/src/components/syncs/content/issues/index.tsx @@ -23,7 +23,7 @@ export const Issues = memo(({ sync }: { sync: SyncPair }) => { const { t } = useTranslation() const virtuosoHeight = useMemo(() => { - return windowSize.height - 64 - DESKTOP_TOPBAR_HEIGHT + return windowSize.height - 64 - 12 - DESKTOP_TOPBAR_HEIGHT }, [windowSize.height]) const errors = useMemo(() => { @@ -62,6 +62,7 @@ export const Issues = memo(({ sync }: { sync: SyncPair }) => { computeItemKey={getItemKey} itemContent={itemContent} components={components} + defaultItemHeight={51} style={{ overflowX: "hidden", overflowY: "auto", diff --git a/src/components/syncs/content/issues/issue.tsx b/src/components/syncs/content/issues/issue.tsx index e6b7472e..7e25baec 100644 --- a/src/components/syncs/content/issues/issue.tsx +++ b/src/components/syncs/content/issues/issue.tsx @@ -4,8 +4,8 @@ import { type GeneralError } from "@/stores/syncs.store" export const Issue = memo(({ error }: { error: GeneralError }) => { return (
    -
    -

    {error.type}

    +
    +

    {error.error.message}

    ) diff --git a/src/components/syncs/content/settings/dialogs/filenIgnore.tsx b/src/components/syncs/content/settings/dialogs/filenIgnore.tsx new file mode 100644 index 00000000..3fade5af --- /dev/null +++ b/src/components/syncs/content/settings/dialogs/filenIgnore.tsx @@ -0,0 +1,201 @@ +import { memo, useState, useEffect, useCallback, useMemo, useRef } from "react" +import { Dialog, DialogContent } from "@/components/ui/dialog" +import eventEmitter from "@/lib/eventEmitter" +import TextEditor from "@/components/textEditor" +import useWindowSize from "@/hooks/useWindowSize" +import { X, Loader } from "lucide-react" +import { useQuery } from "@tanstack/react-query" +import useErrorToast from "@/hooks/useErrorToast" +import { Button } from "@/components/ui/button" +import { useTranslation } from "react-i18next" +import { showConfirmDialog } from "@/components/dialogs/confirm" + +export const Editor = memo( + ({ + syncUUID, + content, + setContent, + setDidChange + }: { + syncUUID: string + content: string + setContent: React.Dispatch> + setDidChange: React.Dispatch> + }) => { + const windowSize = useWindowSize() + const dataUpdatedAtRef = useRef(-1) + + const query = useQuery({ + queryKey: ["syncFetchIgnorerContent", syncUUID], + queryFn: () => window.desktopAPI.syncFetchIgnorerContent({ uuid: syncUUID }) + }) + + const editorHeight = useMemo(() => { + return windowSize.height - 49 + }, [windowSize.height]) + + useEffect(() => { + if (query.isSuccess && query.dataUpdatedAt !== dataUpdatedAtRef.current) { + dataUpdatedAtRef.current = query.dataUpdatedAt + + setContent(query.data) + } + }, [query.isSuccess, query.dataUpdatedAt, query.data, setContent]) + + if (!query.isSuccess) { + return null + } + + return ( + setDidChange(true)} + fileName=".gitignore" + height={editorHeight} + /> + ) + } +) + +export const FilenIgnoreDialog = memo(() => { + const [open, setOpen] = useState(false) + const [syncUUID, setSyncUUID] = useState("") + const [content, setContent] = useState("") + const lastContentRef = useRef("") + const errorToast = useErrorToast() + const [didChange, setDidChange] = useState(false) + const { t } = useTranslation() + const [saving, setSaving] = useState(false) + + const onOpenChange = useCallback((openState: boolean) => { + setOpen(openState) + }, []) + + const updateContent = useCallback(async () => { + if (JSON.stringify(content) === JSON.stringify(lastContentRef.current) || syncUUID.length === 0 || !didChange) { + return + } + + lastContentRef.current = content + + setSaving(true) + + try { + await window.desktopAPI.syncUpdateIgnorerContent({ + uuid: syncUUID, + content + }) + + await window.desktopAPI.syncResetCache({ + uuid: syncUUID + }) + + setDidChange(false) + } catch (e) { + lastContentRef.current = "" + + console.error(e) + + errorToast((e as unknown as Error).message ?? (e as unknown as Error).toString()) + } finally { + setSaving(false) + } + }, [errorToast, syncUUID, didChange, content]) + + const close = useCallback(async () => { + if (didChange) { + if ( + !(await showConfirmDialog({ + title: t("previewDialog.unsavedChanges.title"), + continueButtonText: t("previewDialog.unsavedChanges.continue"), + description: t("previewDialog.unsavedChanges.description"), + continueButtonVariant: "destructive" + })) + ) { + return + } + } + + setOpen(false) + setDidChange(false) + setContent("") + + lastContentRef.current = "" + }, [didChange, t]) + + useEffect(() => { + const listener = eventEmitter.on("openFilenIgnoreDialog", (uuid: string) => { + setOpen(true) + setSyncUUID(uuid) + }) + + return () => { + listener.remove() + } + }, []) + + return ( + + +
    +
    +

    {t("syncs.dialogs.filenIgnore.title")}

    +
    + {didChange && ( + + )} + +
    +
    +
    + +
    +
    +
    +
    + ) +}) + +export default FilenIgnoreDialog diff --git a/src/components/syncs/content/settings/index.tsx b/src/components/syncs/content/settings/index.tsx index 4c9e2047..d1cbaae2 100644 --- a/src/components/syncs/content/settings/index.tsx +++ b/src/components/syncs/content/settings/index.tsx @@ -1,19 +1,61 @@ import { memo, useCallback, useMemo } from "react" -import { type SyncPair } from "@filen/sync/dist/types" +import { type SyncPair, type SyncMode } from "@filen/sync/dist/types" import Section from "@/components/settings/section" import useDesktopConfig from "@/hooks/useDesktopConfig" import { useTranslation } from "react-i18next" import { Switch } from "@/components/ui/switch" import eventEmitter from "@/lib/eventEmitter" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { Info, ChevronsLeftRight, Cloud, ChevronLeft, ChevronRight, Archive, PcCase } from "lucide-react" +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip" +import { TOOLTIP_POPUP_DELAY } from "@/constants" +import useSettingsContainerSize from "@/hooks/useSettingsContainerSize" +import FilenIgnoreDialog from "./dialogs/filenIgnore" +import { useSyncsStore } from "@/stores/syncs.store" +import { Button } from "@/components/ui/button" +import useErrorToast from "@/hooks/useErrorToast" +import useLoadingToast from "@/hooks/useLoadingToast" export const Settings = memo(({ sync }: { sync: SyncPair }) => { - const [desktopConfig] = useDesktopConfig() + const [desktopConfig, setDesktopConfig] = useDesktopConfig() const { t } = useTranslation() + const settingsContainerSize = useSettingsContainerSize() + const { changing, setChanging } = useSyncsStore() + const errorToast = useErrorToast() + const loadingToast = useLoadingToast() const syncConfig = useMemo(() => { return desktopConfig.syncConfig.syncPairs.filter(pair => pair.uuid === sync.uuid)[0] ?? null }, [sync.uuid, desktopConfig]) + const modeToString = useMemo(() => { + switch (syncConfig?.mode) { + case "twoWay": { + return t("dialogs.createSync.mode.twoWay") + } + + case "localToCloud": { + return t("dialogs.createSync.mode.localToCloud") + } + + case "localBackup": { + return t("dialogs.createSync.mode.localBackup") + } + + case "cloudToLocal": { + return t("dialogs.createSync.mode.cloudToLocal") + } + + case "cloudBackup": { + return t("dialogs.createSync.mode.cloudBackup") + } + + default: { + return t("dialogs.createSync.mode.twoWay") + } + } + }, [syncConfig, t]) + const togglePause = useCallback(() => { eventEmitter.emit("toggleSyncPause", sync.uuid) }, [sync.uuid]) @@ -22,29 +64,307 @@ export const Settings = memo(({ sync }: { sync: SyncPair }) => { eventEmitter.emit("deleteSync", sync.uuid) }, [sync.uuid]) + const toggleExcludeDotFiles = useCallback( + async (excludeDotFiles: boolean) => { + setChanging(true) + + const toast = loadingToast() + + try { + await window.desktopAPI.syncUpdateExcludeDotFiles({ + uuid: sync.uuid, + excludeDotFiles + }) + + await window.desktopAPI.syncResetCache({ + uuid: sync.uuid + }) + + setDesktopConfig(prev => ({ + ...prev, + syncConfig: { + ...prev.syncConfig, + syncPairs: prev.syncConfig.syncPairs.map(pair => (pair.uuid === sync.uuid ? { ...pair, excludeDotFiles } : pair)) + } + })) + } catch (e) { + console.error(e) + + errorToast((e as unknown as Error).message ?? (e as unknown as Error).toString()) + } finally { + setChanging(false) + + toast.dismiss() + } + }, + [setDesktopConfig, sync.uuid, setChanging, errorToast, loadingToast] + ) + + const onModeChange = useCallback( + async (mode: SyncMode) => { + setChanging(true) + + const toast = loadingToast() + + try { + await window.desktopAPI.syncUpdateMode({ + uuid: sync.uuid, + mode + }) + + await window.desktopAPI.syncResetCache({ + uuid: sync.uuid + }) + + setDesktopConfig(prev => ({ + ...prev, + syncConfig: { + ...prev.syncConfig, + syncPairs: prev.syncConfig.syncPairs.map(pair => (pair.uuid === sync.uuid ? { ...pair, mode } : pair)) + } + })) + } catch (e) { + console.error(e) + + errorToast((e as unknown as Error).message ?? (e as unknown as Error).toString()) + } finally { + setChanging(false) + + toast.dismiss() + } + }, + [setDesktopConfig, sync.uuid, loadingToast, setChanging, errorToast] + ) + + const forceSync = useCallback(async () => { + try { + await window.desktopAPI.syncResetCache({ + uuid: sync.uuid + }) + } catch (e) { + console.error(e) + + errorToast((e as unknown as Error).message ?? (e as unknown as Error).toString()) + } + }, [errorToast, sync.uuid]) + + const editIgnore = useCallback(() => { + eventEmitter.emit("openFilenIgnoreDialog", sync.uuid) + }, [sync.uuid]) + + const openLocalPath = useCallback(async () => { + if (!syncConfig) { + return + } + + try { + await window.desktopAPI.openLocalPath(syncConfig.localPath) + } catch (e) { + console.error(e) + + errorToast((e as unknown as Error).message ?? (e as unknown as Error).toString()) + } + }, [syncConfig, errorToast]) + + if (!syncConfig) { + return null + } + return ( -
    -
    - -
    -
    -

    +

    +
    - {t("syncs.settings.sections.delete.delete")} -

    -
    -
    +
    +
    +
    + + + +

    + {syncConfig.localPath} +

    +
    + +

    {syncConfig.localPath}

    +
    +
    +
    +
    +
    + + + +
    + {syncConfig.mode === "twoWay" ? ( + + ) : syncConfig.mode === "cloudBackup" ? ( + <> + + + + ) : syncConfig.mode === "localBackup" ? ( + <> + + + + ) : syncConfig.mode === "localToCloud" ? ( + <> + + + + ) : syncConfig.mode === "cloudToLocal" ? ( + <> + + + + ) : ( + + )} +
    +
    + +

    {modeToString}

    +
    +
    +
    +
    +
    + + + +

    {syncConfig.remotePath}

    +
    + +

    {syncConfig.remotePath}

    +
    +
    +
    +
    +
    +
    + +
    +
    +
    + + + + + + + +
    +
    +

    {t("dialogs.createSync.mode.twoWay")}

    +

    {t("dialogs.createSync.mode.twoWayInfo")}

    +
    +
    +

    {t("dialogs.createSync.mode.localToCloud")}

    +

    {t("dialogs.createSync.mode.localToCloudInfo")}

    +
    +
    +

    {t("dialogs.createSync.mode.localBackup")}

    +

    {t("dialogs.createSync.mode.localBackupInfo")}

    +
    +
    +

    {t("dialogs.createSync.mode.cloudToLocal")}

    +

    {t("dialogs.createSync.mode.cloudToLocalInfo")}

    +
    +
    +

    {t("dialogs.createSync.mode.cloudBackup")}

    +

    {t("dialogs.createSync.mode.cloudBackupInfo")}

    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +

    + {t("syncs.settings.sections.filenIgnore.edit")} +

    +
    +
    + +
    +
    + +
    +
    +
    +
    + + ) }) diff --git a/src/components/syncs/content/transfers/index.tsx b/src/components/syncs/content/transfers/index.tsx new file mode 100644 index 00000000..7b11ded3 --- /dev/null +++ b/src/components/syncs/content/transfers/index.tsx @@ -0,0 +1,76 @@ +import { memo, useMemo, useRef, useCallback } from "react" +import { type SyncPair } from "@filen/sync/dist/types" +import { useSyncsStore, type Transfer as TransferType } from "@/stores/syncs.store" +import { Virtuoso, type VirtuosoHandle } from "react-virtuoso" +import useWindowSize from "@/hooks/useWindowSize" +import Transfer from "./transfer" +import { type LocalTreeIgnoredReason } from "@filen/sync/dist/lib/filesystems/local" +import { type RemoteTreeIgnoredReason } from "@filen/sync/dist/lib/filesystems/remote" +import { DESKTOP_TOPBAR_HEIGHT } from "@/constants" +import { ArrowDownUp } from "lucide-react" +import { useTranslation } from "react-i18next" + +export type IgnoreType = { + localPath: string + relativePath: string + reason: LocalTreeIgnoredReason | RemoteTreeIgnoredReason +} + +export const Transfers = memo(({ sync }: { sync: SyncPair }) => { + const { transfers: syncTransfers } = useSyncsStore() + const virtuosoRef = useRef(null) + const windowSize = useWindowSize() + const { t } = useTranslation() + + const virtuosoHeight = useMemo(() => { + return windowSize.height - 64 - 12 - DESKTOP_TOPBAR_HEIGHT + }, [windowSize.height]) + + const transfers = useMemo(() => { + return syncTransfers[sync.uuid] ? syncTransfers[sync.uuid]! : [] + }, [sync.uuid, syncTransfers]) + + const getItemKey = useCallback((_: number, transfer: TransferType) => `${transfer.localPath}:${transfer.relativePath}`, []) + + const itemContent = useCallback((_: number, transfer: TransferType) => { + return + }, []) + + const components = useMemo(() => { + return { + EmptyPlaceholder: () => { + return ( +
    + +

    {t("syncs.noTransfers")}

    +
    + ) + } + } + }, [t]) + + return ( + + ) +}) + +export default Transfers diff --git a/src/components/syncs/content/transfers/transfer.tsx b/src/components/syncs/content/transfers/transfer.tsx new file mode 100644 index 00000000..42901ae6 --- /dev/null +++ b/src/components/syncs/content/transfers/transfer.tsx @@ -0,0 +1,64 @@ +import { memo, useMemo } from "react" +import { type Transfer as TransferType } from "@/stores/syncs.store" +import { Progress } from "@/components/ui/progress" +import { cn } from "@/lib/utils" +import { fileNameToSVGIcon } from "@/assets/fileExtensionIcons" +import { formatBytes } from "@/utils" +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip" +import { TOOLTIP_POPUP_DELAY } from "@/constants" + +export const Transfer = memo(({ transfer }: { transfer: TransferType }) => { + const progressNormalized = useMemo(() => { + return parseInt(((transfer.bytes / transfer.size) * 100).toFixed(0)) + }, [transfer.bytes, transfer.size]) + + return ( +
    +
    +
    +
    +
    + +
    +
    +
    + + + +

    {transfer.name}

    +
    + +

    {transfer.type === "upload" ? transfer.localPath : transfer.relativePath}

    +
    +
    +
    +

    {formatBytes(transfer.size)}

    +
    +
    +
    + = 99 + ? 99 + : progressNormalized + } + color="green" + className={cn( + "w-full h-[6px]", + transfer.state === "finished" ? "progress-finished" : "", + transfer.state === "error" ? "progress-error" : "" + )} + /> +
    + ) +}) + +export default Transfer diff --git a/src/components/syncs/index.tsx b/src/components/syncs/index.tsx index 94020e7f..16f6d910 100644 --- a/src/components/syncs/index.tsx +++ b/src/components/syncs/index.tsx @@ -1,7 +1,7 @@ import { memo, useCallback } from "react" import { useSyncsStore } from "@/stores/syncs.store" import { cn } from "@/lib/utils" -import { RefreshCcw, Plus } from "lucide-react" +import { RefreshCw, Plus } from "lucide-react" import { Button } from "@/components/ui/button" import { useTranslation } from "react-i18next" import eventEmitter from "@/lib/eventEmitter" @@ -30,7 +30,7 @@ export const Syncs = memo(() => { ) : desktopConfig.syncConfig.syncPairs.length === 0 ? (
    - void maxLength?: number }) => { + const codeMirrorRef = useRef(null) const publicLinkURLState = usePublicLinkURLState() const theme = useTheme() const location = useLocation() @@ -135,6 +136,18 @@ export const TextEditor = memo( ] }, [type, langExtension]) + const onCreateEditor = useCallback((view: EditorView) => { + view.dispatch({ + scrollIntoView: true + }) + }, []) + + useEffect(() => { + codeMirrorRef.current?.view?.dispatch({ + scrollIntoView: true + }) + }, []) + return (
    = { started: 1, @@ -32,6 +35,9 @@ export const Transfers = memo(() => { const allBytes = useRef(0) const progressStarted = useRef(-1) const [remainingReadable, setRemainingReadable] = useState("") + const [paused, setPaused] = useState(false) + const isTogglingPauseOrAbort = useRef(false) + const errorToast = useErrorToast() const onDragOver = useCallback( (e: React.DragEvent) => { @@ -172,8 +178,6 @@ export const Transfers = memo(() => { } else if (message.data.type === "progress") { const bytes = message.data.bytes - bytesSent.current += bytes - setTransfers(prev => prev.map(transfer => transfer.uuid === message.data.uuid @@ -185,6 +189,8 @@ export const Transfers = memo(() => { : transfer ) ) + + bytesSent.current += bytes } else if (message.data.type === "finished") { setFinishedTransfers(prev => [ ...prev, @@ -202,12 +208,9 @@ export const Transfers = memo(() => { progressTimestamp: 0 } ]) + setTransfers(prev => prev.filter(transfer => transfer.uuid !== message.data.uuid)) } else if (message.data.type === "error") { - if (allBytes.current >= message.data.size) { - allBytes.current -= message.data.size - } - setTransfers(prev => prev.map(transfer => transfer.uuid === message.data.uuid @@ -219,12 +222,16 @@ export const Transfers = memo(() => { : transfer ) ) - } else if (message.data.type === "stopped") { + if (allBytes.current >= message.data.size) { allBytes.current -= message.data.size } - + } else if (message.data.type === "stopped") { setTransfers(prev => prev.filter(transfer => transfer.uuid !== message.data.uuid)) + + if (allBytes.current >= message.data.size) { + allBytes.current -= message.data.size + } } updateInfo() @@ -233,8 +240,106 @@ export const Transfers = memo(() => { [setFinishedTransfers, setTransfers, updateInfo] ) + const abort = useCallback(async () => { + if (isTogglingPauseOrAbort.current) { + return + } + + isTogglingPauseOrAbort.current = true + + try { + await Promise.all( + transfers.map(async transfer => { + const progressNormalized = parseInt(((transfer.bytes / transfer.size) * 100).toFixed(0)) + + if (transfer.state === "stopped" || transfer.state === "error" || progressNormalized >= 95) { + return + } + + if (transfer.type === "download" && IS_DESKTOP) { + await window.desktopAPI.abortAbortSignal({ id: transfer.uuid }) + } else { + await worker.abortAbortSignal({ id: transfer.uuid }) + } + + setTransfers(prev => prev.filter(t => t.uuid !== transfer.uuid)) + }) + ) + + setPaused(false) + } catch (e) { + console.error(e) + + errorToast((e as unknown as Error).message ?? (e as unknown as Error).toString()) + } finally { + isTogglingPauseOrAbort.current = false + } + }, [errorToast, transfers, setTransfers]) + + const togglePause = useCallback(async () => { + if (isTogglingPauseOrAbort.current) { + return + } + + isTogglingPauseOrAbort.current = true + + try { + if (paused) { + await Promise.all( + transfers.map(async transfer => { + if (transfer.state === "stopped" || transfer.state === "error" || transfer.state === "finished") { + return + } + + if (transfer.type === "download" && IS_DESKTOP) { + await window.desktopAPI.resumePauseSignal({ id: transfer.uuid }) + } else { + await worker.resumePauseSignal({ id: transfer.uuid }) + } + + setTransfers(prev => prev.map(t => (t.uuid === transfer.uuid ? { ...t, state: "started" } : t))) + }) + ) + + setPaused(false) + } else { + await Promise.all( + transfers.map(async transfer => { + const progressNormalized = parseInt(((transfer.bytes / transfer.size) * 100).toFixed(0)) + + if ( + transfer.state === "stopped" || + transfer.state === "error" || + transfer.state === "finished" || + transfer.state === "paused" || + progressNormalized >= 95 + ) { + return + } + + if (transfer.type === "download" && IS_DESKTOP) { + await window.desktopAPI.pausePauseSignal({ id: transfer.uuid }) + } else { + await worker.pausePauseSignal({ id: transfer.uuid }) + } + + setTransfers(prev => prev.map(t => (t.uuid === transfer.uuid ? { ...t, state: "paused" } : t))) + }) + ) + + setPaused(true) + } + } catch (e) { + console.error(e) + + errorToast((e as unknown as Error).message ?? (e as unknown as Error).toString()) + } finally { + isTogglingPauseOrAbort.current = false + } + }, [paused, errorToast, transfers, setTransfers]) + useEffect(() => { - if (ongoingTransfers.length === 0) { + if (ongoingTransfers.length <= 0) { bytesSent.current = 0 progressStarted.current = -1 allBytes.current = 0 @@ -242,6 +347,7 @@ export const Transfers = memo(() => { setRemaining(0) setSpeed(0) setProgress(0) + setPaused(false) } }, [ongoingTransfers, setRemaining, setSpeed, setProgress]) @@ -291,6 +397,7 @@ export const Transfers = memo(() => { computeItemKey={getItemKey} itemContent={itemContent} onDragOver={onDragOver} + defaultItemHeight={78} components={{ EmptyPlaceholder: () => { return ( @@ -313,12 +420,41 @@ export const Transfers = memo(() => { width: "100%" }} /> -
    - {remaining > 0 && speed > 0 && ( - <> -

    {remainingReadable}

    -

    {bpsToReadable(speed)}

    - +
    + {remaining > 0 && remainingReadable.length > 0 && ( +

    {remainingReadable}

    + )} + {speed > 0 &&

    {bpsToReadable(speed)}

    } + {ongoingTransfers.length > 0 && ( +
    + {paused ? ( + + ) : ( + + )} + +
    )}
    diff --git a/src/components/transfers/transfer.tsx b/src/components/transfers/transfer.tsx index 869d9365..033e5da0 100644 --- a/src/components/transfers/transfer.tsx +++ b/src/components/transfers/transfer.tsx @@ -6,6 +6,10 @@ import { cn } from "@/lib/utils" import { type TFunction } from "i18next" import worker from "@/lib/worker" import { IS_DESKTOP } from "@/constants" +import { fileNameToSVGIcon } from "@/assets/fileExtensionIcons" +import { formatBytes } from "@/utils" +import { Button } from "../ui/button" +import { Play, Pause, XCircle } from "lucide-react" export const Transfer = memo( ({ @@ -81,6 +85,88 @@ export const Transfer = memo( } }, [transfer.uuid, transfer.state, setTransfers, progressNormalized, transfer.type]) + return ( +
    +
    +
    +
    +
    + +
    +
    +
    +

    {transfer.name}

    +

    + {formatBytes(transfer.size)} +

    +
    +
    +
    + {transfer.state === "error" || transfer.state === "queued" ? ( + + {t("transfers.state." + transfer.state)} + + ) : ( + <> + {progressNormalized < 95 && ( + <> + {transfer.state === "paused" ? ( + + ) : ( + + )} + + + )} + + )} +
    +
    + = 99 + ? 99 + : progressNormalized + } + color="green" + className={cn( + "w-full h-[6px]", + transfer.state === "finished" ? "progress-finished" : "", + transfer.state === "error" ? "progress-error" : "" + )} + /> +
    + ) + return (
    diff --git a/src/hooks/useErrorToast.tsx b/src/hooks/useErrorToast.tsx index 04d5d1a3..c2151ba1 100644 --- a/src/hooks/useErrorToast.tsx +++ b/src/hooks/useErrorToast.tsx @@ -2,7 +2,10 @@ import { useToast, type ToasterToast } from "@/components/ui/use-toast" import { Ban } from "lucide-react" import { useCallback } from "react" -export type UseErrorToast = (reason: string) => { +export type UseErrorToast = ( + reason: string, + duration: number +) => { id: string dismiss: () => void update: (props: ToasterToast) => void diff --git a/src/hooks/useIsSyncActive.tsx b/src/hooks/useIsSyncActive.tsx new file mode 100644 index 00000000..f29f5796 --- /dev/null +++ b/src/hooks/useIsSyncActive.tsx @@ -0,0 +1,35 @@ +import { useSyncsStore } from "@/stores/syncs.store" +import { useMemo } from "react" + +export default function useIsSyncActive(uuid?: string): boolean { + const { cycleState } = useSyncsStore() + + const state = useMemo(() => { + if (!uuid) { + const keys = Object.keys(cycleState) + + if (keys.length === 0) { + return false + } + + return keys.some( + syncUUID => + cycleState[syncUUID] === "cycleApplyingStateStarted" || + cycleState[syncUUID] === "cycleProcessingTasksStarted" || + cycleState[syncUUID] === "cycleProcessingDeltasStarted" || + cycleState[syncUUID] === "cycleWaitingForLocalDirectoryChangesStarted" || + cycleState[syncUUID] === "cycleSavingStateStarted" + ) + } + + return cycleState[uuid] + ? cycleState[uuid] === "cycleApplyingStateStarted" || + cycleState[uuid] === "cycleProcessingTasksStarted" || + cycleState[uuid] === "cycleProcessingDeltasStarted" || + cycleState[uuid] === "cycleWaitingForLocalDirectoryChangesStarted" || + cycleState[uuid] === "cycleSavingStateStarted" + : false + }, [cycleState, uuid]) + + return state +} diff --git a/src/lib/worker/worker.ts b/src/lib/worker/worker.ts index 7a78ef38..8d61b9dc 100644 --- a/src/lib/worker/worker.ts +++ b/src/lib/worker/worker.ts @@ -51,67 +51,12 @@ import DOMPurify from "dompurify" const parseOGFromURLMutex = new Semaphore(1) const corsHeadMutex = new Semaphore(1) let isInitialized = false -const postMessageToMainProgressThrottle: Record = {} const pauseSignals: Record = {} const abortControllers: Record = {} const textEncoder = new TextEncoder() -// We have to throttle the "progress" events of the "download"/"upload" message type. The SDK sends too many events for the IPC to handle properly. -// It freezes the main process if we don't throttle it. -function throttlePostMessageToMain(message: WorkerToMainMessage, callback: (message: WorkerToMainMessage) => void) { - const now = Date.now() - let key = "" - - if (message.type === "download" || message.type === "upload") { - if (message.data.type === "progress") { - key = `${message.type}:${message.data.uuid}:${message.data.name}:${message.data.type}` - - if (!postMessageToMainProgressThrottle[key]) { - postMessageToMainProgressThrottle[key] = { - next: 0, - storedBytes: 0 - } - } - - postMessageToMainProgressThrottle[key]!.storedBytes += message.data.bytes - - if (postMessageToMainProgressThrottle[key]!.next > now) { - return - } - - message = { - ...message, - data: { - ...message.data, - bytes: postMessageToMainProgressThrottle[key]!.storedBytes - } - } - } - } - - callback(message) - - if (key.length > 0 && postMessageToMainProgressThrottle[key] && (message.type === "download" || message.type === "upload")) { - postMessageToMainProgressThrottle[key]!.storedBytes = 0 - postMessageToMainProgressThrottle[key]!.next = now + 100 - - if ( - message.data.type === "error" || - message.data.type === "queued" || - message.data.type === "stopped" || - message.data.type === "finished" - ) { - delete postMessageToMainProgressThrottle[key] - } - } -} - // We setup an eventEmitter first here in case we are running in the main thread. -let postMessageToMain: (message: WorkerToMainMessage) => void = message => { - throttlePostMessageToMain(message, msg => { - eventEmitter.emit("workerMessage", msg) - }) -} +let postMessageToMain: (message: WorkerToMainMessage) => void = message => eventEmitter.emit("workerMessage", message) export async function waitForInitialization(): Promise { // Only check for init if we are running inside a WebWorker. @@ -139,7 +84,7 @@ export async function initializeSDK(config: FilenSDKConfig): Promise { } export async function setMessageHandler(callback: (message: WorkerToMainMessage) => void): Promise { - postMessageToMain = message => throttlePostMessageToMain(message, callback) + postMessageToMain = callback return } @@ -658,7 +603,7 @@ export async function uploadFile({ await waitForInitialization() const fileName = name ? name : file.name - const fileId = `${fileName}:${file.size}:${file.type}:${file.lastModified}:${file.webkitRelativePath}` + const fileId = uuidv4() if (!pauseSignals[fileId]) { pauseSignals[fileId] = new PauseSignal() @@ -2778,7 +2723,7 @@ export async function uploadFilesToChatUploads({ files }: { files: File[] }): Pr onlyDirectories: true }) let parentUUID = "" - const baseFiltered = base.filter(item => item.type === "directory" && item.name.toLowerCase() === "chat uploads") + const baseFiltered = base.filter(item => item.type === "directory" && item.name.toLowerCase().trim() === "chat uploads") if (baseFiltered.length === 1 && baseFiltered[0]) { parentUUID = baseFiltered[0].uuid diff --git a/src/stores/mounts.store.ts b/src/stores/mounts.store.ts new file mode 100644 index 00000000..037e34ba --- /dev/null +++ b/src/stores/mounts.store.ts @@ -0,0 +1,25 @@ +import { create } from "zustand" + +export type MountsStore = { + enablingVirtualDrive: boolean + enablingS3: boolean + enablingWebDAV: boolean + setEnablingVirtualDrive: (fn: boolean | ((prev: boolean) => boolean)) => void + setEnablingS3: (fn: boolean | ((prev: boolean) => boolean)) => void + setEnablingWebDAV: (fn: boolean | ((prev: boolean) => boolean)) => void +} + +export const useMountsStore = create(set => ({ + enablingVirtualDrive: false, + enablingS3: false, + enablingWebDAV: false, + setEnablingVirtualDrive(fn) { + set(state => ({ enablingVirtualDrive: typeof fn === "function" ? fn(state.enablingVirtualDrive) : fn })) + }, + setEnablingS3(fn) { + set(state => ({ enablingVirtualDrive: typeof fn === "function" ? fn(state.enablingVirtualDrive) : fn })) + }, + setEnablingWebDAV(fn) { + set(state => ({ enablingVirtualDrive: typeof fn === "function" ? fn(state.enablingVirtualDrive) : fn })) + } +})) diff --git a/src/stores/syncs.store.ts b/src/stores/syncs.store.ts index 9c2b4dcf..b6ac2d63 100644 --- a/src/stores/syncs.store.ts +++ b/src/stores/syncs.store.ts @@ -38,7 +38,13 @@ export type SyncsStore = { errors: Record taskErrors: Record search: string + changing: boolean + remainingReadable: Record + remaining: Record + speed: Record + progress: Record setSelectedSync: (fn: SyncPair | null | ((prev: SyncPair | null) => SyncPair | null)) => void + setChanging: (fn: boolean | ((prev: boolean) => boolean)) => void setTransferEvents: ( fn: | Record @@ -55,18 +61,58 @@ export type SyncsStore = { setErrors: (fn: Record | ((prev: Record) => Record)) => void setTaskErrors: (fn: Record | ((prev: Record) => Record)) => void setSearch: (fn: string | ((prev: string) => string)) => void + setRemainingReadable: (fn: Record | ((prev: Record) => Record)) => void + setRemaining: (fn: Record | ((prev: Record) => Record)) => void + setSpeed: (fn: Record | ((prev: Record) => Record)) => void + setProgress: (fn: Record | ((prev: Record) => Record)) => void } export const useSyncsStore = create(set => ({ selectedSync: null, transferEvents: {}, cycleState: {}, - transfers: {}, + transfers: { + /*"8e9b6c9b-50c2-4bad-9388-4194388c4597": [ + { + type: "download", + localPath: "C:\\Users\\dwynr\\lol.txt", + relativePath: "/lol.txt", + state: "started", + bytes: 1, + name: "lol.txt", + size: 5, + startedTimestamp: 0, + finishedTimestamp: 0, + queuedTimestamp: 0, + errorTimestamp: 0, + progressTimestamp: 0 + }, + { + type: "download", + localPath: "C:\\Users\\dwynr\\lol.txt", + relativePath: "/lol.txt", + state: "started", + bytes: 3, + name: "lol.txt", + size: 5, + startedTimestamp: 0, + finishedTimestamp: 0, + queuedTimestamp: 0, + errorTimestamp: 0, + progressTimestamp: 0 + } + ]*/ + }, remoteIgnored: {}, localIgnored: {}, errors: {}, taskErrors: {}, search: "", + changing: false, + remainingReadable: {}, + speed: {}, + progress: {}, + remaining: {}, setSelectedSync(fn) { set(state => ({ selectedSync: typeof fn === "function" ? fn(state.selectedSync) : fn })) }, @@ -93,5 +139,20 @@ export const useSyncsStore = create(set => ({ }, setSearch(fn) { set(state => ({ search: typeof fn === "function" ? fn(state.search) : fn })) + }, + setChanging(fn) { + set(state => ({ changing: typeof fn === "function" ? fn(state.changing) : fn })) + }, + setRemainingReadable(fn) { + set(state => ({ remainingReadable: typeof fn === "function" ? fn(state.remainingReadable) : fn })) + }, + setProgress(fn) { + set(state => ({ progress: typeof fn === "function" ? fn(state.progress) : fn })) + }, + setRemaining(fn) { + set(state => ({ remaining: typeof fn === "function" ? fn(state.remaining) : fn })) + }, + setSpeed(fn) { + set(state => ({ speed: typeof fn === "function" ? fn(state.speed) : fn })) } })) diff --git a/src/stores/transfers.store.ts b/src/stores/transfers.store.ts index 2c8bf6ed..71790a04 100644 --- a/src/stores/transfers.store.ts +++ b/src/stores/transfers.store.ts @@ -30,7 +30,34 @@ export type TransfersStore = { } export const useTransfersStore = create(set => ({ - transfers: [], + transfers: [ + /*{ + type: "upload", + uuid: "uuid", + state: "started", + bytes: 1, + name: "foo.txt", + size: 5, + startedTimestamp: 0, + finishedTimestamp: 0, + queuedTimestamp: 0, + errorTimestamp: 0, + progressTimestamp: 0 + }, + { + type: "upload", + uuid: "uuid2", + state: "started", + bytes: 3, + name: "foo.txt", + size: 5, + startedTimestamp: 0, + finishedTimestamp: 0, + queuedTimestamp: 0, + errorTimestamp: 0, + progressTimestamp: 0 + }*/ + ], finishedTransfers: [], speed: 0, remaining: 0, diff --git a/tailwind.config.js b/tailwind.config.js index 086d46e8..4ce111cc 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -57,7 +57,6 @@ module.exports = { md: "calc(var(--radius) - 2px)", sm: "calc(var(--radius) - 4px)" }, - keyframes: { "accordion-down": { from: { height: "0" },