diff --git a/README.md b/README.md index a75ae1046d..250f71abb3 100644 --- a/README.md +++ b/README.md @@ -115,46 +115,46 @@ Stirling-PDF currently supports 40 languages! | Language | Progress | | -------------------------------------------- | -------------------------------------- | -| Arabic (العربية) (ar_AR) | ![97%](https://geps.dev/progress/97) | -| Azerbaijani (Azərbaycan Dili) (az_AZ) | ![36%](https://geps.dev/progress/36) | -| Basque (Euskara) (eu_ES) | ![21%](https://geps.dev/progress/21) | +| Arabic (العربية) (ar_AR) | ![95%](https://geps.dev/progress/95) | +| Azerbaijani (Azərbaycan Dili) (az_AZ) | ![35%](https://geps.dev/progress/35) | +| Basque (Euskara) (eu_ES) | ![20%](https://geps.dev/progress/20) | | Bulgarian (Български) (bg_BG) | ![39%](https://geps.dev/progress/39) | -| Catalan (Català) (ca_CA) | ![38%](https://geps.dev/progress/38) | -| Croatian (Hrvatski) (hr_HR) | ![35%](https://geps.dev/progress/35) | -| Czech (Česky) (cs_CZ) | ![39%](https://geps.dev/progress/39) | +| Catalan (Català) (ca_CA) | ![37%](https://geps.dev/progress/37) | +| Croatian (Hrvatski) (hr_HR) | ![34%](https://geps.dev/progress/34) | +| Czech (Česky) (cs_CZ) | ![38%](https://geps.dev/progress/38) | | Danish (Dansk) (da_DK) | ![34%](https://geps.dev/progress/34) | | Dutch (Nederlands) (nl_NL) | ![34%](https://geps.dev/progress/34) | | English (English) (en_GB) | ![100%](https://geps.dev/progress/100) | | English (US) (en_US) | ![100%](https://geps.dev/progress/100) | -| French (Français) (fr_FR) | ![95%](https://geps.dev/progress/95) | -| German (Deutsch) (de_DE) | ![97%](https://geps.dev/progress/97) | -| Greek (Ελληνικά) (el_GR) | ![39%](https://geps.dev/progress/39) | -| Hindi (हिंदी) (hi_IN) | ![39%](https://geps.dev/progress/39) | -| Hungarian (Magyar) (hu_HU) | ![43%](https://geps.dev/progress/43) | -| Indonesian (Bahasa Indonesia) (id_ID) | ![35%](https://geps.dev/progress/35) | -| Irish (Gaeilge) (ga_IE) | ![39%](https://geps.dev/progress/39) | -| Italian (Italiano) (it_IT) | ![97%](https://geps.dev/progress/97) | -| Japanese (日本語) (ja_JP) | ![72%](https://geps.dev/progress/72) | -| Korean (한국어) (ko_KR) | ![39%](https://geps.dev/progress/39) | +| French (Français) (fr_FR) | ![93%](https://geps.dev/progress/93) | +| German (Deutsch) (de_DE) | ![95%](https://geps.dev/progress/95) | +| Greek (Ελληνικά) (el_GR) | ![38%](https://geps.dev/progress/38) | +| Hindi (हिंदी) (hi_IN) | ![38%](https://geps.dev/progress/38) | +| Hungarian (Magyar) (hu_HU) | ![42%](https://geps.dev/progress/42) | +| Indonesian (Bahasa Indonesia) (id_ID) | ![34%](https://geps.dev/progress/34) | +| Irish (Gaeilge) (ga_IE) | ![38%](https://geps.dev/progress/38) | +| Italian (Italiano) (it_IT) | ![95%](https://geps.dev/progress/95) | +| Japanese (日本語) (ja_JP) | ![70%](https://geps.dev/progress/70) | +| Korean (한국어) (ko_KR) | ![38%](https://geps.dev/progress/38) | | Norwegian (Norsk) (no_NB) | ![36%](https://geps.dev/progress/36) | | Persian (فارسی) (fa_IR) | ![38%](https://geps.dev/progress/38) | -| Polish (Polski) (pl_PL) | ![41%](https://geps.dev/progress/41) | -| Portuguese (Português) (pt_PT) | ![39%](https://geps.dev/progress/39) | -| Portuguese Brazilian (Português) (pt_BR) | ![97%](https://geps.dev/progress/97) | -| Romanian (Română) (ro_RO) | ![33%](https://geps.dev/progress/33) | -| Russian (Русский) (ru_RU) | ![96%](https://geps.dev/progress/96) | +| Polish (Polski) (pl_PL) | ![40%](https://geps.dev/progress/40) | +| Portuguese (Português) (pt_PT) | ![38%](https://geps.dev/progress/38) | +| Portuguese Brazilian (Português) (pt_BR) | ![95%](https://geps.dev/progress/95) | +| Romanian (Română) (ro_RO) | ![32%](https://geps.dev/progress/32) | +| Russian (Русский) (ru_RU) | ![94%](https://geps.dev/progress/94) | | Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![42%](https://geps.dev/progress/42) | -| Simplified Chinese (简体中文) (zh_CN) | ![98%](https://geps.dev/progress/98) | -| Slovakian (Slovensky) (sk_SK) | ![29%](https://geps.dev/progress/29) | +| Simplified Chinese (简体中文) (zh_CN) | ![96%](https://geps.dev/progress/96) | +| Slovakian (Slovensky) (sk_SK) | ![28%](https://geps.dev/progress/28) | | Slovenian (Slovenščina) (sl_SI) | ![40%](https://geps.dev/progress/40) | -| Spanish (Español) (es_ES) | ![97%](https://geps.dev/progress/97) | -| Swedish (Svenska) (sv_SE) | ![38%](https://geps.dev/progress/38) | -| Thai (ไทย) (th_TH) | ![35%](https://geps.dev/progress/35) | +| Spanish (Español) (es_ES) | ![95%](https://geps.dev/progress/95) | +| Swedish (Svenska) (sv_SE) | ![37%](https://geps.dev/progress/37) | +| Thai (ไทย) (th_TH) | ![34%](https://geps.dev/progress/34) | | Tibetan (བོད་ཡིག་) (bo_CN) | ![65%](https://geps.dev/progress/65) | -| Traditional Chinese (繁體中文) (zh_TW) | ![43%](https://geps.dev/progress/43) | -| Turkish (Türkçe) (tr_TR) | ![42%](https://geps.dev/progress/42) | -| Ukrainian (Українська) (uk_UA) | ![41%](https://geps.dev/progress/41) | -| Vietnamese (Tiếng Việt) (vi_VN) | ![32%](https://geps.dev/progress/32) | +| Traditional Chinese (繁體中文) (zh_TW) | ![42%](https://geps.dev/progress/42) | +| Turkish (Türkçe) (tr_TR) | ![41%](https://geps.dev/progress/41) | +| Ukrainian (Українська) (uk_UA) | ![40%](https://geps.dev/progress/40) | +| Vietnamese (Tiếng Việt) (vi_VN) | ![31%](https://geps.dev/progress/31) | | Malayalam (മലയാളം) (ml_IN) | ![73%](https://geps.dev/progress/73) | ## Stirling PDF Enterprise diff --git a/app/common/src/main/java/stirling/software/common/configuration/AppConfig.java b/app/common/src/main/java/stirling/software/common/configuration/AppConfig.java index b8fa08739d..3bcc48715b 100644 --- a/app/common/src/main/java/stirling/software/common/configuration/AppConfig.java +++ b/app/common/src/main/java/stirling/software/common/configuration/AppConfig.java @@ -258,12 +258,6 @@ public class AppConfig { return false; } - @Bean(name = "GoogleDriveEnabled") - @Profile("default") - public boolean googleDriveEnabled() { - return false; - } - @Bean(name = "license") @Profile("default") public String licenseType() { diff --git a/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java b/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java index 7eeace787a..14704d8257 100644 --- a/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java +++ b/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java @@ -530,7 +530,6 @@ public class ApplicationProperties { private boolean ssoAutoLogin; private boolean database; private CustomMetadata customMetadata = new CustomMetadata(); - private GoogleDrive googleDrive = new GoogleDrive(); @Data public static class CustomMetadata { @@ -549,26 +548,6 @@ public class ApplicationProperties { : producer; } } - - @Data - public static class GoogleDrive { - private boolean enabled; - private String clientId; - private String apiKey; - private String appId; - - public String getClientId() { - return clientId == null || clientId.trim().isEmpty() ? "" : clientId; - } - - public String getApiKey() { - return apiKey == null || apiKey.trim().isEmpty() ? "" : apiKey; - } - - public String getAppId() { - return appId == null || appId.trim().isEmpty() ? "" : appId; - } - } } @Data diff --git a/app/common/src/test/java/stirling/software/common/model/ApplicationPropertiesLogicTest.java b/app/common/src/test/java/stirling/software/common/model/ApplicationPropertiesLogicTest.java index da83fd4620..66078099fe 100644 --- a/app/common/src/test/java/stirling/software/common/model/ApplicationPropertiesLogicTest.java +++ b/app/common/src/test/java/stirling/software/common/model/ApplicationPropertiesLogicTest.java @@ -109,22 +109,6 @@ class ApplicationPropertiesLogicTest { assertTrue(ex.getMessage().toLowerCase().contains("not supported")); } - @Test - void premium_google_drive_getters_return_empty_string_on_null_or_blank() { - Premium.ProFeatures.GoogleDrive gd = new Premium.ProFeatures.GoogleDrive(); - - assertEquals("", gd.getClientId()); - assertEquals("", gd.getApiKey()); - assertEquals("", gd.getAppId()); - - gd.setClientId(" id "); - gd.setApiKey(" key "); - gd.setAppId(" app "); - assertEquals(" id ", gd.getClientId()); - assertEquals(" key ", gd.getApiKey()); - assertEquals(" app ", gd.getAppId()); - } - @Test void ui_getters_return_null_for_blank() { ApplicationProperties.Ui ui = new ApplicationProperties.Ui(); diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ConfigController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ConfigController.java index 6d9263270e..072471e5cd 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ConfigController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ConfigController.java @@ -98,11 +98,6 @@ public class ConfigController { if (applicationContext.containsBean("license")) { configData.put("license", applicationContext.getBean("license", String.class)); } - if (applicationContext.containsBean("GoogleDriveEnabled")) { - configData.put( - "GoogleDriveEnabled", - applicationContext.getBean("GoogleDriveEnabled", Boolean.class)); - } if (applicationContext.containsBean("SSOAutoLogin")) { configData.put( "SSOAutoLogin", diff --git a/app/core/src/main/resources/messages_en_GB.properties b/app/core/src/main/resources/messages_en_GB.properties index 405643877d..99fb34b0db 100644 --- a/app/core/src/main/resources/messages_en_GB.properties +++ b/app/core/src/main/resources/messages_en_GB.properties @@ -1946,6 +1946,25 @@ viewer.zoomIn=Zoom in # Tool Picker toolPicker.searchPlaceholder=Search tools... toolPicker.noToolsFound=No tools found +toolPanel.toggle.legacy=Switch to legacy mode +toolPanel.toggle.sidebar=Switch to sidebar mode +toolPanel.placeholder=Choose a tool to get started +toolPanel.legacy.heading=All tools (legacy view) +toolPanel.legacy.tagline=Browse and launch tools while keeping the classic full-width gallery. +toolPanel.legacy.descriptionsOn=Showing descriptions +toolPanel.legacy.descriptionsOff=Descriptions hidden +toolPanel.legacy.noResults=Try adjusting your search or toggle descriptions to find what you need. +toolPanel.legacy.matchedSynonym=Matches "{{text}}" +toolPanel.modePrompt.title=Choose how you browse tools +toolPanel.modePrompt.description=Preview both layouts and decide how you want to explore Stirling PDF tools. +toolPanel.modePrompt.sidebarTitle=Advanced sidebar +toolPanel.modePrompt.sidebarDescription=Keep tools alongside your workspace for quick switching. +toolPanel.modePrompt.recommended=Recommended +toolPanel.modePrompt.chooseSidebar=Use advanced sidebar +toolPanel.modePrompt.legacyTitle=Legacy fullscreen +toolPanel.modePrompt.legacyDescription=Browse every tool in a catalogue that covers the workspace until you pick one. +toolPanel.modePrompt.chooseLegacy=Use legacy fullscreen +toolPanel.modePrompt.dismiss=Maybe later pageEditor.reset=Reset Changes pageEditor.zoomIn=Zoom In pageEditor.zoomOut=Zoom Out @@ -1958,3 +1977,39 @@ viewer.nextPage=Next Page viewer.pageNavigation=Page Navigation viewer.currentPage=Current Page viewer.totalPages=Total Pages +toolPanel.legacy.favorites=Favourites +toolPanel.legacy.recent=Recently used +toolPanel.legacy.favorite=Add to favourites +toolPanel.legacy.unfavorite=Remove from favourites +toolPanel.legacy.settings.title=Customise appearance +toolPanel.legacy.settings.iconBackground.label=Tool icon background +toolPanel.legacy.settings.iconBackground.description=When to show coloured backgrounds behind tool icons +toolPanel.legacy.settings.iconBackground.none=None +toolPanel.legacy.settings.iconBackground.hover=On hover +toolPanel.legacy.settings.iconBackground.always=Always +toolPanel.legacy.settings.iconColor.label=Tool icon colour +toolPanel.legacy.settings.iconColor.description=Colour scheme for tool icons +toolPanel.legacy.settings.iconColor.colored=Coloured +toolPanel.legacy.settings.iconColor.vibrant=Vibrant +toolPanel.legacy.settings.iconColor.monochrome=Monochrome +toolPanel.legacy.settings.sectionTitle.label=Section titles +toolPanel.legacy.settings.sectionTitle.description=Colour for category section titles +toolPanel.legacy.settings.sectionTitle.colored=Coloured +toolPanel.legacy.settings.sectionTitle.neutral=Neutral +toolPanel.legacy.settings.headerIcon.label=Section header icons +toolPanel.legacy.settings.headerIcon.description=Colour for Favourites/Recent icons +toolPanel.legacy.settings.headerIcon.colored=Coloured +toolPanel.legacy.settings.headerIcon.monochrome=Monochrome +toolPanel.legacy.settings.headerBadge.label=Section header badges +toolPanel.legacy.settings.headerBadge.description=Colour for count badges in section headers +toolPanel.legacy.settings.headerBadge.colored=Coloured +toolPanel.legacy.settings.headerBadge.neutral=Neutral +toolPanel.legacy.settings.border.label=Tool item borders +toolPanel.legacy.settings.border.description=Show borders around tool items +toolPanel.legacy.settings.border.visible=Visible +toolPanel.legacy.settings.border.hidden=Hidden +toolPanel.legacy.settings.hover.label=Hover effect intensity +toolPanel.legacy.settings.hover.description=How prominent the hover effect should be +toolPanel.legacy.settings.hover.subtle=Subtle +toolPanel.legacy.settings.hover.moderate=Moderate +toolPanel.legacy.settings.hover.prominent=Prominent diff --git a/app/core/src/main/resources/settings.yml.template b/app/core/src/main/resources/settings.yml.template index 465f95fb6e..8143ba4c29 100644 --- a/app/core/src/main/resources/settings.yml.template +++ b/app/core/src/main/resources/settings.yml.template @@ -76,11 +76,6 @@ premium: author: username creator: Stirling-PDF producer: Stirling-PDF - googleDrive: - enabled: false - clientId: '' - apiKey: '' - appId: '' enterpriseFeatures: audit: enabled: true # Enable audit logging diff --git a/app/core/src/main/resources/templates/fragments/common.html b/app/core/src/main/resources/templates/fragments/common.html index d3b888a1d7..973822b583 100644 --- a/app/core/src/main/resources/templates/fragments/common.html +++ b/app/core/src/main/resources/templates/fragments/common.html @@ -422,10 +422,6 @@ -
- -
- google drive
@@ -443,16 +439,4 @@ - -
- - - - - -
diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/ee/EEAppConfig.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/ee/EEAppConfig.java index 215b823479..2e71b670d9 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/ee/EEAppConfig.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/ee/EEAppConfig.java @@ -12,7 +12,6 @@ import org.springframework.core.annotation.Order; import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties.EnterpriseEdition; import stirling.software.common.model.ApplicationProperties.Premium; -import stirling.software.common.model.ApplicationProperties.Premium.ProFeatures.GoogleDrive; @Configuration @Order(Ordered.HIGHEST_PRECEDENCE) @@ -55,19 +54,6 @@ public class EEAppConfig { return applicationProperties.getPremium().getProFeatures().isSsoAutoLogin(); } - @Profile("security") - @Bean(name = "GoogleDriveEnabled") - @Primary - public boolean googleDriveEnabled() { - return runningProOrHigher() - && applicationProperties.getPremium().getProFeatures().getGoogleDrive().isEnabled(); - } - - @Bean(name = "GoogleDriveConfig") - public GoogleDrive googleDriveConfig() { - return applicationProperties.getPremium().getProFeatures().getGoogleDrive(); - } - // TODO: Remove post migration @SuppressWarnings("deprecation") public void migrateEnterpriseSettingsToPremium(ApplicationProperties applicationProperties) { diff --git a/docker/README.md b/docker/README.md index df07e6b9e8..99b86b53ea 100644 --- a/docker/README.md +++ b/docker/README.md @@ -50,7 +50,14 @@ docker-compose -f docker/compose/docker-compose.fat.yml up --build - **Custom Ports**: Modify port mappings in docker-compose files - **Memory Limits**: Adjust memory limits per variant (2G ultra-lite, 4G standard, 6G fat) +### [Google Drive Integration](https://developers.google.com/workspace/drive/picker/guides/overview) + +- **VITE_GOOGLE_DRIVE_CLIENT_ID**: [OAuth 2.0 Client ID](https://console.cloud.google.com/auth/clients/create) +- **VITE_GOOGLE_DRIVE_API_KEY**: [Create New API](https://console.cloud.google.com/apis) +- **VITE_GOOGLE_DRIVE_APP_ID**: This is your [project number](https://console.cloud.google.com/iam-admin/settings) in the GoogleCloud Settings + ## Development vs Production - **Development**: Keep backend port 8080 exposed for debugging -- **Production**: Remove backend port exposure, use only frontend proxy \ No newline at end of file +- **Production**: Remove backend port exposure, use only frontend proxy + diff --git a/docker/compose/docker-compose.fat.yml b/docker/compose/docker-compose.fat.yml index 1757782d5e..d0242eb3cb 100644 --- a/docker/compose/docker-compose.fat.yml +++ b/docker/compose/docker-compose.fat.yml @@ -47,6 +47,9 @@ services: - "3000:80" environment: BACKEND_URL: http://backend:8080 + #VITE_GOOGLE_DRIVE_CLIENT_ID: + #VITE_GOOGLE_DRIVE_API_KEY: + #VITE_GOOGLE_DRIVE_APP_ID: depends_on: - backend networks: diff --git a/docker/compose/docker-compose.ultra-lite.yml b/docker/compose/docker-compose.ultra-lite.yml index bfbf55861c..0639b53aca 100644 --- a/docker/compose/docker-compose.ultra-lite.yml +++ b/docker/compose/docker-compose.ultra-lite.yml @@ -44,6 +44,9 @@ services: - "3000:80" environment: BACKEND_URL: http://backend:8080 + #VITE_GOOGLE_DRIVE_CLIENT_ID: + #VITE_GOOGLE_DRIVE_API_KEY: + #VITE_GOOGLE_DRIVE_APP_ID: depends_on: - backend networks: diff --git a/docker/compose/docker-compose.yml b/docker/compose/docker-compose.yml index b0061f7854..6f8b1ace80 100644 --- a/docker/compose/docker-compose.yml +++ b/docker/compose/docker-compose.yml @@ -46,6 +46,9 @@ services: - "3000:80" environment: BACKEND_URL: http://backend:8080 + #VITE_GOOGLE_DRIVE_CLIENT_ID: + #VITE_GOOGLE_DRIVE_API_KEY: + #VITE_GOOGLE_DRIVE_APP_ID: depends_on: - backend networks: diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0146da7536..e5ff22f6fe 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -54,11 +54,12 @@ "react-dom": "^19.1.1", "react-i18next": "^15.7.3", "react-router-dom": "^7.9.1", + "signature_pad": "^5.0.4", "tailwindcss": "^4.1.13", "web-vitals": "^5.1.0" }, "devDependencies": { - "@eslint/js": "^9.36.0", + "@eslint/js": "^9.37.0", "@iconify-json/material-symbols": "^1.2.37", "@iconify/utils": "^3.0.2", "@playwright/test": "^1.55.0", @@ -67,16 +68,20 @@ "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", "@tsconfig/vite-react": "^7.0.1", + "@types/gapi": "^0.0.47", + "@types/gapi.client.drive-v3": "^0.0.5", + "@types/google.accounts": "^0.0.18", + "@types/google.picker": "^0.0.51", "@types/node": "^24.5.2", "@types/react": "^19.1.13", "@types/react-dom": "^19.1.9", - "@typescript-eslint/eslint-plugin": "^8.45.0", - "@typescript-eslint/parser": "^8.45.0", + "@typescript-eslint/eslint-plugin": "^8.46.1", + "@typescript-eslint/parser": "^8.46.1", "@vitejs/plugin-react-swc": "^4.1.0", "@vitest/coverage-v8": "^3.2.4", - "eslint": "^9.36.0", + "eslint": "^9.37.0", "eslint-plugin-react": "^7.37.5", - "eslint-plugin-react-hooks": "^6.1.0", + "eslint-plugin-react-hooks": "^7.0.0", "eslint-plugin-simple-import-sort": "^12.1.1", "globals": "^16.4.0", "jsdom": "^27.0.0", @@ -87,7 +92,7 @@ "postcss-preset-mantine": "^1.18.0", "postcss-simple-vars": "^7.0.1", "typescript": "^5.9.3", - "typescript-eslint": "^8.45.0", + "typescript-eslint": "^8.46.1", "vite": "^7.1.7", "vitest": "^3.2.4" } @@ -140,9 +145,9 @@ } }, "node_modules/@antfu/utils": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-9.2.1.tgz", - "integrity": "sha512-TMilPqXyii1AsiEii6l6ubRzbo76p6oshUSYPaKsmXDavyMLqjzVDkcp3pHp5ELMUNJHATcEOGxKTTsX9yYhGg==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-9.3.0.tgz", + "integrity": "sha512-9hFT4RauhcUzqOE4f1+frMKLZrgNog5b06I7VmZQV1BkvwvqrbC8EBZf3L1eEL2AKb6rNKjER0sEvJiSP1FXEA==", "dev": true, "license": "MIT", "funding": { @@ -163,10 +168,20 @@ "lru-cache": "^11.2.1" } }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/@asamuzakjp/dom-selector": { - "version": "6.5.7", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.5.7.tgz", - "integrity": "sha512-cvdTPsi2qC1c22UppvuVmx/PDwuc6+QQkwt9OnwQD6Uotbh//tb2XDF0OoK2V0F4b8d02LIwNp3BieaDMAhIhA==", + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.6.2.tgz", + "integrity": "sha512-+AG0jN9HTwfDLBhjhX1FKi6zlIAc/YGgEHlN/OMaHD1pOPFsC5CpYQpLkPX0aFjyaVmoq9330cQDCU4qnSL1qA==", "dev": true, "license": "MIT", "dependencies": { @@ -177,6 +192,16 @@ "lru-cache": "^11.2.2" } }, + "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/@asamuzakjp/nwsapi": { "version": "2.3.9", "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", @@ -283,19 +308,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", - "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.3" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-compilation-targets": { "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", @@ -313,16 +325,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -333,45 +335,6 @@ "semver": "bin/semver.js" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz", - "integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-member-expression-to-functions": "^7.27.1", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.28.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/helper-globals": { "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", @@ -381,20 +344,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", - "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-module-imports": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", @@ -426,61 +375,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", - "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", - "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.27.1", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", - "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", @@ -538,24 +432,6 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-proposal-private-methods": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", - "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/runtime": { "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", @@ -773,13 +649,13 @@ } }, "node_modules/@embedpdf/core": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@embedpdf/core/-/core-1.3.11.tgz", - "integrity": "sha512-1ZDR3o0R68TX6H4MlniXp2w5tVPmdNxrVfNM91iDl/iCkq7nHGfippQ5zUMQQd5254xTwI6D2GsraRNp+QnTyQ==", + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@embedpdf/core/-/core-1.3.14.tgz", + "integrity": "sha512-lE/vfhA53CxamaCfGWEibrEPr+JeZT42QCF+cOELUwv4+Zt6b+IE6+4wsznx/8wjjJYwllXJ3GJ/un1UzTqARw==", "license": "MIT", "dependencies": { - "@embedpdf/engines": "1.3.11", - "@embedpdf/models": "1.3.11" + "@embedpdf/engines": "1.3.14", + "@embedpdf/models": "1.3.14" }, "peerDependencies": { "preact": "^10.26.4", @@ -789,13 +665,13 @@ } }, "node_modules/@embedpdf/engines": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@embedpdf/engines/-/engines-1.3.11.tgz", - "integrity": "sha512-3ypRbj+kYOlh3EgMaPMQgAkYIJ18Isw8pwD8Uoyi4lxhROToR0nULQ652YnslKZ82/6NGYbzpThgEWcnJgtQTA==", + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@embedpdf/engines/-/engines-1.3.14.tgz", + "integrity": "sha512-+/FPW2gAzj2lQYvsMH/Oj9+MEXgkyEuyYDC+HFkltTuXvmiP2S/3BD0YslZDX9K4BzcmMxnWB+BiQpNJokbDVg==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.3.11", - "@embedpdf/pdfium": "1.3.11" + "@embedpdf/models": "1.3.14", + "@embedpdf/pdfium": "1.3.14" }, "peerDependencies": { "preact": "^10.26.4", @@ -805,31 +681,31 @@ } }, "node_modules/@embedpdf/models": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@embedpdf/models/-/models-1.3.11.tgz", - "integrity": "sha512-PcoY0OlOFHfUtUZJT+5ZgIXS+9V9UQicht/6MLiva5gKmYOEWMEqw4/ga8rGEwOdBkwZgc5HnXXQvZLPoLl6NQ==", + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@embedpdf/models/-/models-1.3.14.tgz", + "integrity": "sha512-BujY4bmr8b2DQdoZkOge03SzoRVoWxzfIQATLSPPtp4WiFh1U4BPp6cADlGuCwGkp6zBcH/aM4h8PwwA75d/eg==", "license": "MIT" }, "node_modules/@embedpdf/pdfium": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@embedpdf/pdfium/-/pdfium-1.3.11.tgz", - "integrity": "sha512-+yEuQZyyxmUtVeykc72lOnXJayFqgxk9tY9i6XUbUwNoTIG1hngkKaJ1Xge5K2Sn0RK2LioZBW0k6/2/9m6VVw==", + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@embedpdf/pdfium/-/pdfium-1.3.14.tgz", + "integrity": "sha512-TQMZabXzHmzvvfPwopubFcYgQuYV7POvMgjICYu3Pgfn3sgr+UdIUh3aNXR/COcl3q8sXPMFQ2GDuyOHR9QQnA==", "license": "MIT" }, "node_modules/@embedpdf/plugin-annotation": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-annotation/-/plugin-annotation-1.3.11.tgz", - "integrity": "sha512-WS+LXIk7vCN3+r9AaoYoeLJT2PkmSJm8ZbvegCl2UkKk/FYchQjDqWhADhAWbFKJXJP/VTFxODa2+cIeTx8cWQ==", + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-annotation/-/plugin-annotation-1.3.14.tgz", + "integrity": "sha512-JJYqEWwUKCdBZsXCDq/CW96p3pVLn8N+XZ4W3OyL7djI2fvYC9x6ys9m82vwlSathAVOxk1D7xXiY8AzJQVF0Q==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.3.11", - "@embedpdf/utils": "1.3.11" + "@embedpdf/models": "1.3.14", + "@embedpdf/utils": "1.3.14" }, "peerDependencies": { - "@embedpdf/core": "1.3.11", - "@embedpdf/plugin-history": "1.3.11", - "@embedpdf/plugin-interaction-manager": "1.3.11", - "@embedpdf/plugin-selection": "1.3.11", + "@embedpdf/core": "1.3.14", + "@embedpdf/plugin-history": "1.3.14", + "@embedpdf/plugin-interaction-manager": "1.3.14", + "@embedpdf/plugin-selection": "1.3.14", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -837,15 +713,15 @@ } }, "node_modules/@embedpdf/plugin-export": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-export/-/plugin-export-1.3.11.tgz", - "integrity": "sha512-JgI/AcBiW3WWSWDbNQvQFsysiNY7NglAgrQK92piBBdUf+1fuFgTJ5HoiKPD6ZcjGLpMpJaVcIgM8j6trXLzvQ==", + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-export/-/plugin-export-1.3.14.tgz", + "integrity": "sha512-fMGp2YxvI4uTRIViUKxfnJts2Jw/vktEM45XUNGNSjT/kAW6znVNgdceYjpK++xU8CGs2grAQ1i5UvMd3aRNDA==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.3.11" + "@embedpdf/models": "1.3.14" }, "peerDependencies": { - "@embedpdf/core": "1.3.11", + "@embedpdf/core": "1.3.14", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -853,15 +729,15 @@ } }, "node_modules/@embedpdf/plugin-history": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-history/-/plugin-history-1.3.11.tgz", - "integrity": "sha512-PT8wy1x+OAPfOLz/8yZkkLM1tEzuuH0/uClVozm/J9XTSSO58b0RzD/MThGJlPrA8P7XSS6yv7FY3V/siSXrhA==", + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-history/-/plugin-history-1.3.14.tgz", + "integrity": "sha512-77hnNLp0W0FHw8lT7SeqzCgp8bOClfeOAPZdcInu/jPDhVASUGYbtE/0fkLhiaqPH7kyMirNCLif4sF6n4b5vg==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.3.11" + "@embedpdf/models": "1.3.14" }, "peerDependencies": { - "@embedpdf/core": "1.3.11", + "@embedpdf/core": "1.3.14", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -869,15 +745,15 @@ } }, "node_modules/@embedpdf/plugin-interaction-manager": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-interaction-manager/-/plugin-interaction-manager-1.3.11.tgz", - "integrity": "sha512-C8lwDC6SHt9/uPIyNUwAYAzyNsLVw4JIV9kH/zwo3X0wEt6kvhf6yJHNJIKWyPlsqQcdzDcGkMOeWA3bONwLhQ==", + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-interaction-manager/-/plugin-interaction-manager-1.3.14.tgz", + "integrity": "sha512-nR0ZxNoTQtGqOHhweFh6QJ+nUJ4S4Ag1wWur6vAUAi8U95HUOfZhOEa0polZo0zR9WmmblGqRWjFM+mVSOoi1w==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.3.11" + "@embedpdf/models": "1.3.14" }, "peerDependencies": { - "@embedpdf/core": "1.3.11", + "@embedpdf/core": "1.3.14", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -885,15 +761,15 @@ } }, "node_modules/@embedpdf/plugin-loader": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-loader/-/plugin-loader-1.3.11.tgz", - "integrity": "sha512-MgWSOG6dVR8LAwlRdE5SyOdP13glLMw4V2xhmnPmdXTvDDLIwyquvedjzcQpQqpp5aUQ9bj69zNP70CxpZEKGw==", + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-loader/-/plugin-loader-1.3.14.tgz", + "integrity": "sha512-KoJX1MacEWE2DrO1OeZeG/Ehz76//u+ida/xb4r9BfwqAp5TfYlksq09cOvcF8LMW5FY4pbAL+AHKI1Hjz+HNA==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.3.11" + "@embedpdf/models": "1.3.14" }, "peerDependencies": { - "@embedpdf/core": "1.3.11", + "@embedpdf/core": "1.3.14", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -901,17 +777,17 @@ } }, "node_modules/@embedpdf/plugin-pan": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-pan/-/plugin-pan-1.3.11.tgz", - "integrity": "sha512-VWhPUsP9tjiHVy87HrxszObsVtmjBhJVF8LmeTM9bHFrRBXNv2/BR3+OMoMsmPythnVsG+fDDjitUpOKlBEa7g==", + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-pan/-/plugin-pan-1.3.14.tgz", + "integrity": "sha512-7EG+I5nn8yDCV8pT4x/g5mv7zJli2t3wPrh6Kt8uIpUorPHNb6J0Z67gl0uc/8rEasNzuKOuT0er46Y6/UYLzQ==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.3.11" + "@embedpdf/models": "1.3.14" }, "peerDependencies": { - "@embedpdf/core": "1.3.11", - "@embedpdf/plugin-interaction-manager": "1.3.11", - "@embedpdf/plugin-viewport": "1.3.11", + "@embedpdf/core": "1.3.14", + "@embedpdf/plugin-interaction-manager": "1.3.14", + "@embedpdf/plugin-viewport": "1.3.14", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -919,15 +795,15 @@ } }, "node_modules/@embedpdf/plugin-render": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-render/-/plugin-render-1.3.11.tgz", - "integrity": "sha512-NiTk5E8X3qWLXoft3CceJ60+ep8/7PDFKf4asoFO0iHFeSXotbTq96Q7mWc1yGI6IW2gWYnF7JbmCr01+X5RFA==", + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-render/-/plugin-render-1.3.14.tgz", + "integrity": "sha512-IPj7GCQXJBsY++JaU+z7y+FwX5NaDBj4YYV6hsHNtSGf42Y1AdlwJzDYetivG2bA84xmk7KgD1X2Y3eIFBhjwA==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.3.11" + "@embedpdf/models": "1.3.14" }, "peerDependencies": { - "@embedpdf/core": "1.3.11", + "@embedpdf/core": "1.3.14", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -935,15 +811,15 @@ } }, "node_modules/@embedpdf/plugin-rotate": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-rotate/-/plugin-rotate-1.3.11.tgz", - "integrity": "sha512-ySxcD715BC/haQgaSfxOB1WPJPT+w7YxE5GsncRymiId8+96hlNI3iWdrGN9Li8fadSgCozawT9RFBAPar72fw==", + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-rotate/-/plugin-rotate-1.3.14.tgz", + "integrity": "sha512-OroEm11x/fPPXI9C0X+nm9LOjwaI0MvsToZRH+HpV60/FbQeOJvt6D8wThCDVLK95Na6A+JeYIMEu+Hiix7H+A==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.3.11" + "@embedpdf/models": "1.3.14" }, "peerDependencies": { - "@embedpdf/core": "1.3.11", + "@embedpdf/core": "1.3.14", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -951,16 +827,16 @@ } }, "node_modules/@embedpdf/plugin-scroll": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-scroll/-/plugin-scroll-1.3.11.tgz", - "integrity": "sha512-6qTlkFq0CltzIU1AWq9aFE+lf0+3n7hCb2Ez+mJW9BD1wz2bFqsD2J8J6gQtLgbDGvXNoc/UGXPdOiOyj+/imA==", + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-scroll/-/plugin-scroll-1.3.14.tgz", + "integrity": "sha512-fQbt7OlRMLQJMuZj/Bzh0qpRxMw1ld5Qe/OTw8N54b/plljnFA52joE7cITl3H03huWWyHS3NKOScbw7f34dog==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.3.11" + "@embedpdf/models": "1.3.14" }, "peerDependencies": { - "@embedpdf/core": "1.3.11", - "@embedpdf/plugin-viewport": "1.3.11", + "@embedpdf/core": "1.3.14", + "@embedpdf/plugin-viewport": "1.3.14", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -968,16 +844,16 @@ } }, "node_modules/@embedpdf/plugin-search": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-search/-/plugin-search-1.3.11.tgz", - "integrity": "sha512-RJh0/AZC0KVe333a3t/crT1zOjxWQusxvA1dmSEblp2vdVeDNy5Q5l+uDn1aQviojI19BtK/XlcfS/C9q1lUww==", + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-search/-/plugin-search-1.3.14.tgz", + "integrity": "sha512-tlZEgR2tG+GSNnh2u1SjCxhUHfTDgcr38sE/xRK1bRLDGPZWlr6Ln7qP7JSWqeYBGni75sGrj0iZqcZbPWyJag==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.3.11" + "@embedpdf/models": "1.3.14" }, "peerDependencies": { - "@embedpdf/core": "1.3.11", - "@embedpdf/plugin-loader": "1.3.11", + "@embedpdf/core": "1.3.14", + "@embedpdf/plugin-loader": "1.3.14", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -985,17 +861,17 @@ } }, "node_modules/@embedpdf/plugin-selection": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-selection/-/plugin-selection-1.3.11.tgz", - "integrity": "sha512-s5A7lpH+5lVqslPHTBJtxv7WLMTs1rdGa9g1UQD9Pma3594V5/kl2Wfexu8GnXTLCsOX/7nMDehFy4iJuxV6sQ==", + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-selection/-/plugin-selection-1.3.14.tgz", + "integrity": "sha512-EXENuaAsse3rT6cjA1nYzyrNvoy62ojJl28wblCng6zcs3HSlGPemIQZAvaYKPUxoY608M+6nKlcMQ5neRnk/A==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.3.11" + "@embedpdf/models": "1.3.14" }, "peerDependencies": { - "@embedpdf/core": "1.3.11", - "@embedpdf/plugin-interaction-manager": "1.3.11", - "@embedpdf/plugin-viewport": "1.3.11", + "@embedpdf/core": "1.3.14", + "@embedpdf/plugin-interaction-manager": "1.3.14", + "@embedpdf/plugin-viewport": "1.3.14", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -1003,16 +879,16 @@ } }, "node_modules/@embedpdf/plugin-spread": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-spread/-/plugin-spread-1.3.11.tgz", - "integrity": "sha512-nKL9uqix+jTML3tQBpQiHDv0qC6Ee+7VLI6Bs3jcK9yv97AGzZ6/vWOtZBt8KQtKbF9R3Sp+qFIHJrCFlSQR+Q==", + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-spread/-/plugin-spread-1.3.14.tgz", + "integrity": "sha512-DVlk6tDgUoDRkp2S4Jc3LrRTuf4DPMlph9vywJw5z6Qpbh0vgcMnObg896/S0Eu5FgACNAj0WGcXpLrcrn5b9Q==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.3.11" + "@embedpdf/models": "1.3.14" }, "peerDependencies": { - "@embedpdf/core": "1.3.11", - "@embedpdf/plugin-loader": "1.3.11", + "@embedpdf/core": "1.3.14", + "@embedpdf/plugin-loader": "1.3.14", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -1020,16 +896,16 @@ } }, "node_modules/@embedpdf/plugin-thumbnail": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-thumbnail/-/plugin-thumbnail-1.3.11.tgz", - "integrity": "sha512-qfDP9zDlv2F9hEsonCQ/9XW09f8QB56WBSYh78oHwrAztFvD8LntuNHLIvXysb05zWmxYv1tJ3HZygZACfZIxA==", + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-thumbnail/-/plugin-thumbnail-1.3.14.tgz", + "integrity": "sha512-cnwb5dG8Jph8XSArys1WFCQ6kK2R5FKoO0B5mDrHFv9Fcm2pKszlmZC/NDoskX4pgNUgSnwhI1X3cP37ebF9Ng==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.3.11" + "@embedpdf/models": "1.3.14" }, "peerDependencies": { - "@embedpdf/core": "1.3.11", - "@embedpdf/plugin-render": "1.3.11", + "@embedpdf/core": "1.3.14", + "@embedpdf/plugin-render": "1.3.14", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -1037,18 +913,18 @@ } }, "node_modules/@embedpdf/plugin-tiling": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-tiling/-/plugin-tiling-1.3.11.tgz", - "integrity": "sha512-m2GgwG0FBI7opsY3SlzrchkEDDhvGaj+fOEgaEusWoYUZIlHzmBCGOvELffFE7YbWXaXlOfYU3gggxeBZM/rQQ==", + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-tiling/-/plugin-tiling-1.3.14.tgz", + "integrity": "sha512-SaCTo2LdZwGeE6jCqkwJxvwt8YKbsI3QGxa9S7Ez+5OcBchlhHeTfLQswcErDQ3WH2p8WHtGuucAcOLrVVOm0A==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.3.11" + "@embedpdf/models": "1.3.14" }, "peerDependencies": { - "@embedpdf/core": "1.3.11", - "@embedpdf/plugin-render": "1.3.11", - "@embedpdf/plugin-scroll": "1.3.11", - "@embedpdf/plugin-viewport": "1.3.11", + "@embedpdf/core": "1.3.14", + "@embedpdf/plugin-render": "1.3.14", + "@embedpdf/plugin-scroll": "1.3.14", + "@embedpdf/plugin-viewport": "1.3.14", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -1056,15 +932,15 @@ } }, "node_modules/@embedpdf/plugin-viewport": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-viewport/-/plugin-viewport-1.3.11.tgz", - "integrity": "sha512-oGIgqnD4jeDzrF4Jyu4WTzydi6Jtuof4y0Vr4k7ukxwch0LrgdhpgbORilgSlK2saV/0ZnI4+aQxw9Hsa6k/uQ==", + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-viewport/-/plugin-viewport-1.3.14.tgz", + "integrity": "sha512-mfJ7EbbU68eKk6oFvQ4ozGJNpxUxWbjQ5Gm3uuB+Gj5/tWgBocBOX36k/9LgivEEeX7g2S0tOgyErljApmH8Vg==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.3.11" + "@embedpdf/models": "1.3.14" }, "peerDependencies": { - "@embedpdf/core": "1.3.11", + "@embedpdf/core": "1.3.14", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -1072,19 +948,19 @@ } }, "node_modules/@embedpdf/plugin-zoom": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@embedpdf/plugin-zoom/-/plugin-zoom-1.3.11.tgz", - "integrity": "sha512-AWwtg4yPJFQMXQ5MshJHBD03Fbslum0IYHADetzPCbqZMCRDxg0B9VSKkHvP2VBro66lDPdjSmjIGXf0HAP8RQ==", + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-zoom/-/plugin-zoom-1.3.14.tgz", + "integrity": "sha512-/N5tyMk+8OzhObrS3O9yPkcmX8EPiuTo+WaT2QCVSmIUqKnOO4AnKpHJ6Vl0uVhcuXHCMwLucZKyhJ7tRqavwg==", "license": "MIT", "dependencies": { - "@embedpdf/models": "1.3.11", + "@embedpdf/models": "1.3.14", "hammerjs": "^2.0.8" }, "peerDependencies": { - "@embedpdf/core": "1.3.11", - "@embedpdf/plugin-interaction-manager": "1.3.11", - "@embedpdf/plugin-scroll": "1.3.11", - "@embedpdf/plugin-viewport": "1.3.11", + "@embedpdf/core": "1.3.14", + "@embedpdf/plugin-interaction-manager": "1.3.14", + "@embedpdf/plugin-scroll": "1.3.14", + "@embedpdf/plugin-viewport": "1.3.14", "preact": "^10.26.4", "react": ">=16.8.0", "react-dom": ">=16.8.0", @@ -1092,9 +968,9 @@ } }, "node_modules/@embedpdf/utils": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@embedpdf/utils/-/utils-1.3.11.tgz", - "integrity": "sha512-5bpviVisMxhnfaYKTXSR1gkC14y5MDsL5TPIHi9FYiX6ZW0wFdhml0hO7yjJTYk7cE3H8RigRqgcIxlKczRRsQ==", + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@embedpdf/utils/-/utils-1.3.14.tgz", + "integrity": "sha512-gxEJD12nageCMqAjdbicNfDQolXU3nvnV0EX96OdZITRNj0Q1tisutVYoaxcCiJu3vvIEOzipjsAnQOubbFCEA==", "license": "MIT", "peerDependencies": { "preact": "^10.26.4", @@ -1250,9 +1126,9 @@ "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", - "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz", + "integrity": "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==", "cpu": [ "ppc64" ], @@ -1267,9 +1143,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz", - "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.11.tgz", + "integrity": "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==", "cpu": [ "arm" ], @@ -1284,9 +1160,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz", - "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.11.tgz", + "integrity": "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==", "cpu": [ "arm64" ], @@ -1301,9 +1177,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz", - "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.11.tgz", + "integrity": "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==", "cpu": [ "x64" ], @@ -1318,9 +1194,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz", - "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.11.tgz", + "integrity": "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==", "cpu": [ "arm64" ], @@ -1335,9 +1211,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz", - "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.11.tgz", + "integrity": "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==", "cpu": [ "x64" ], @@ -1352,9 +1228,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz", - "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.11.tgz", + "integrity": "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==", "cpu": [ "arm64" ], @@ -1369,9 +1245,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz", - "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.11.tgz", + "integrity": "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==", "cpu": [ "x64" ], @@ -1386,9 +1262,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz", - "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.11.tgz", + "integrity": "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==", "cpu": [ "arm" ], @@ -1403,9 +1279,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz", - "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.11.tgz", + "integrity": "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==", "cpu": [ "arm64" ], @@ -1420,9 +1296,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz", - "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.11.tgz", + "integrity": "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==", "cpu": [ "ia32" ], @@ -1437,9 +1313,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz", - "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.11.tgz", + "integrity": "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==", "cpu": [ "loong64" ], @@ -1454,9 +1330,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz", - "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.11.tgz", + "integrity": "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==", "cpu": [ "mips64el" ], @@ -1471,9 +1347,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz", - "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.11.tgz", + "integrity": "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==", "cpu": [ "ppc64" ], @@ -1488,9 +1364,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz", - "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.11.tgz", + "integrity": "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==", "cpu": [ "riscv64" ], @@ -1505,9 +1381,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz", - "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.11.tgz", + "integrity": "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==", "cpu": [ "s390x" ], @@ -1522,9 +1398,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz", - "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.11.tgz", + "integrity": "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==", "cpu": [ "x64" ], @@ -1539,9 +1415,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz", - "integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.11.tgz", + "integrity": "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==", "cpu": [ "arm64" ], @@ -1556,9 +1432,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz", - "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.11.tgz", + "integrity": "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==", "cpu": [ "x64" ], @@ -1573,9 +1449,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz", - "integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.11.tgz", + "integrity": "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==", "cpu": [ "arm64" ], @@ -1590,9 +1466,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz", - "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.11.tgz", + "integrity": "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==", "cpu": [ "x64" ], @@ -1607,9 +1483,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz", - "integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.11.tgz", + "integrity": "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==", "cpu": [ "arm64" ], @@ -1624,9 +1500,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz", - "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.11.tgz", + "integrity": "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==", "cpu": [ "x64" ], @@ -1641,9 +1517,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz", - "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.11.tgz", + "integrity": "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==", "cpu": [ "arm64" ], @@ -1658,9 +1534,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz", - "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.11.tgz", + "integrity": "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==", "cpu": [ "ia32" ], @@ -1675,9 +1551,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz", - "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.11.tgz", + "integrity": "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==", "cpu": [ "x64" ], @@ -1760,19 +1636,22 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", - "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.0.tgz", + "integrity": "sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.16.0" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/core": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", - "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz", + "integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1854,9 +1733,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.36.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.36.0.tgz", - "integrity": "sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==", + "version": "9.37.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.37.0.tgz", + "integrity": "sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg==", "dev": true, "license": "MIT", "engines": { @@ -1877,13 +1756,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", - "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.0.tgz", + "integrity": "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.15.2", + "@eslint/core": "^0.16.0", "levn": "^0.4.1" }, "engines": { @@ -1996,9 +1875,9 @@ } }, "node_modules/@iconify-json/material-symbols": { - "version": "1.2.40", - "resolved": "https://registry.npmjs.org/@iconify-json/material-symbols/-/material-symbols-1.2.40.tgz", - "integrity": "sha512-utXUazVsZ/cXJCKVIOdqLgwgmuy3Yx0dJtTk771d+O7NSdklUR42thl0H8L5db07I+TMS56BzPAf8vEDH68tdQ==", + "version": "1.2.41", + "resolved": "https://registry.npmjs.org/@iconify-json/material-symbols/-/material-symbols-1.2.41.tgz", + "integrity": "sha512-lYhQtRot/YtRweG4+yiulU2BKwv63Uuj1z/BNUs9TrSno8nJ6ufiQfi4uXSuvRumMg3kwO35WxZ1ZhPAvbY8bA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2232,10 +2111,16 @@ "integrity": "sha512-e4psVV9Fe2eBfS9xK2rzQ9lE5xS4tARm7EJzDb6sVZy3F+EMyHJ67i0NdBVR9BRyQx7YhogMCbB6R1QwXuBxMg==", "license": "MIT" }, + "node_modules/@keyv/serialize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", + "license": "MIT" + }, "node_modules/@mantine/core": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/@mantine/core/-/core-8.3.2.tgz", - "integrity": "sha512-uIHC9ooEZ9E+/pw8ag4f8pi0GmwSQ1DYnETjr4a4ZNVKJHfVv5NSkjprBxPrKJq9oox/SdcrAWy5XlKTwBzRag==", + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@mantine/core/-/core-8.3.5.tgz", + "integrity": "sha512-PdVNLMgOS2vFhOujRi6/VC9ic8w3UDyKX7ftwDeJ7yQT8CiepUxfbWWYpVpnq23bdWh/7fIT2Pn1EY8r8GOk7g==", "license": "MIT", "dependencies": { "@floating-ui/react": "^0.27.16", @@ -2246,55 +2131,77 @@ "type-fest": "^4.41.0" }, "peerDependencies": { - "@mantine/hooks": "8.3.2", + "@mantine/hooks": "8.3.5", "react": "^18.x || ^19.x", "react-dom": "^18.x || ^19.x" } }, "node_modules/@mantine/dates": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/@mantine/dates/-/dates-8.3.2.tgz", - "integrity": "sha512-qO9Aft+icFGSeLFTbbHfef/UIKpmUzwujsYuRFw8o6cqOqhqjlC9ObE/3DATxvS+vK9BxODUZYGtE2sI4XUO3Q==", + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@mantine/dates/-/dates-8.3.5.tgz", + "integrity": "sha512-LkIdC4eWPNQFv1BU1c52U3Z3RuA3yU1asvTgMEIQ/MdJsGK8GePwpgMH/jKQ8ba/AW9NfksdvtOJ6uIqPwjCkg==", "license": "MIT", "dependencies": { "clsx": "^2.1.1" }, "peerDependencies": { - "@mantine/core": "8.3.2", - "@mantine/hooks": "8.3.2", + "@mantine/core": "8.3.5", + "@mantine/hooks": "8.3.5", "dayjs": ">=1.0.0", "react": "^18.x || ^19.x", "react-dom": "^18.x || ^19.x" } }, "node_modules/@mantine/dropzone": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/@mantine/dropzone/-/dropzone-8.3.2.tgz", - "integrity": "sha512-2NInRbW0M+mNfBLa90P4zN0UfXs8bUWiKRR7Fw9qYV+xWML1O4HMoNcFRB2kLPZbb3ld0d2bMXfW+0j/GOhLuw==", + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@mantine/dropzone/-/dropzone-8.3.5.tgz", + "integrity": "sha512-8eRNoEYQzUOav8Te58osGRt2vbqJO3ZORXgKALY+FhB0YGRCZYmS/gQ2T66SpyrpovY6k6OVi1ScM3wRaJrxUg==", "license": "MIT", "dependencies": { "react-dropzone": "14.3.8" }, "peerDependencies": { - "@mantine/core": "8.3.2", - "@mantine/hooks": "8.3.2", + "@mantine/core": "8.3.5", + "@mantine/hooks": "8.3.5", "react": "^18.x || ^19.x", "react-dom": "^18.x || ^19.x" } }, "node_modules/@mantine/hooks": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-8.3.2.tgz", - "integrity": "sha512-urDgQJNAs2t2mAyGaA+7uNsBMRn9U/ccvi+ZUl5ef3/Wzfv5KYHe9LA9DBNhn24BTSewxrI27W0EFpFxv/Jsbg==", + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-8.3.5.tgz", + "integrity": "sha512-0Wf08eWLKi3WkKlxnV1W5vfuN6wcvAV2VbhQlOy0R9nrWorGTtonQF6qqBE3PnJFYF1/ZE+HkYZQ/Dr7DmYSMQ==", "license": "MIT", "peerDependencies": { "react": "^18.x || ^19.x" } }, + "node_modules/@maxim_mazurok/gapi.client.discovery-v1": { + "version": "0.4.20200806", + "resolved": "https://registry.npmjs.org/@maxim_mazurok/gapi.client.discovery-v1/-/gapi.client.discovery-v1-0.4.20200806.tgz", + "integrity": "sha512-Jeo/KZqK39DI6ExXHcJ4lqnn1O/wEqboQ6eQ8WnNpu5eJ7wUnX/C5KazOgs1aRhnIB/dVzDe8wm62nmtkMIoaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/gapi.client": "*", + "@types/gapi.client.discovery-v1": "*" + } + }, + "node_modules/@maxim_mazurok/gapi.client.drive-v3": { + "version": "0.1.20251007", + "resolved": "https://registry.npmjs.org/@maxim_mazurok/gapi.client.drive-v3/-/gapi.client.drive-v3-0.1.20251007.tgz", + "integrity": "sha512-HV8WovC7B1b0MKXoNexzJcO9gS13zwNLHYeCB93mxR2to1YZFnhYhaWqSTOlni72T/GtSgfFo1ypTOabbCPbPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/gapi.client": "*", + "@types/gapi.client.discovery-v1": "*" + } + }, "node_modules/@mui/core-downloads-tracker": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.3.tgz", - "integrity": "sha512-iQzXRJpur0lMt0oV65rvPYopRlcrVy9wwNMPvjmbAe0nOrY2BF4gMBO+5lZc8MxFoUtPRNsEEQmndkGqdqjTEA==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.4.tgz", + "integrity": "sha512-BIktMapG3r4iXwIhYNpvk97ZfYWTreBBQTWjQKbNbzI64+ULHfYavQEX2w99aSWHS58DvXESWIgbD9adKcUOBw==", "license": "MIT", "funding": { "type": "opencollective", @@ -2302,9 +2209,9 @@ } }, "node_modules/@mui/icons-material": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.3.3.tgz", - "integrity": "sha512-c/eD59CubuTDPmExgsO9LP9yvDquD9QFZY3eT9l/UJiayJgRe3Cr9JantDmQT26Gc0lozIVGMpJtMTjzDsCYpg==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.3.4.tgz", + "integrity": "sha512-9n6Xcq7molXWYb680N2Qx+FRW8oT6j/LXF5PZFH3ph9X/Rct0B/BlLAsFI7iL9ySI6LVLuQIVtrLiPT82R7OZw==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.28.4" @@ -2317,7 +2224,7 @@ "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "@mui/material": "^7.3.3", + "@mui/material": "^7.3.4", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, @@ -2328,13 +2235,13 @@ } }, "node_modules/@mui/material": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.3.tgz", - "integrity": "sha512-MM+pqCPvVgnbzB2DLZkqfvHKzFTJMHcxCvuDhAJvHfJeqODScIn9SuG6GVuyhxAvnnhx+3DcUiwiR83tVET1cw==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.4.tgz", + "integrity": "sha512-gEQL9pbJZZHT7lYJBKQCS723v1MGys2IFc94COXbUIyCTWa+qC77a7hUax4Yjd5ggEm35dk4AyYABpKKWC4MLw==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.28.4", - "@mui/core-downloads-tracker": "^7.3.3", + "@mui/core-downloads-tracker": "^7.3.4", "@mui/system": "^7.3.3", "@mui/types": "^7.4.7", "@mui/utils": "^7.3.3", @@ -2777,13 +2684,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.55.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.55.1.tgz", - "integrity": "sha512-IVAh/nOJaw6W9g+RJVlIQJ6gSiER+ae6mKQ5CX1bERzQgbC1VSeBlwdvczT7pxb0GWiyrxH4TGKbMfDb4Sq/ig==", + "version": "1.56.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.0.tgz", + "integrity": "sha512-Tzh95Twig7hUwwNe381/K3PggZBZblKUe2wv25oIpzWLr6Z0m4KgV1ZVIjnR6GM9ANEqjZD7XsZEa6JL/7YEgg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.55.1" + "playwright": "1.56.0" }, "bin": { "playwright": "cli.js" @@ -2803,9 +2710,9 @@ } }, "node_modules/@posthog/core": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.2.2.tgz", - "integrity": "sha512-f16Ozx6LIigRG+HsJdt+7kgSxZTHeX5f1JlCGKI1lXcvlZgfsCR338FuMI2QRYXGl+jg/vYFzGOTQBxl90lnBg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.3.0.tgz", + "integrity": "sha512-hxLL8kZNHH098geedcxCz8y6xojkNYbmJEW+1vFXsmPcExyCXIUUJ/34X6xa9GcprKxd0Wsx3vfJQLQX4iVPhw==", "license": "MIT" }, "node_modules/@rolldown/pluginutils": { @@ -2816,9 +2723,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.3.tgz", - "integrity": "sha512-h6cqHGZ6VdnwliFG1NXvMPTy/9PS3h8oLh7ImwR+kl+oYnQizgjxsONmmPSb2C66RksfkfIxEVtDSEcJiO0tqw==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz", + "integrity": "sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==", "cpu": [ "arm" ], @@ -2830,9 +2737,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.3.tgz", - "integrity": "sha512-wd+u7SLT/u6knklV/ifG7gr5Qy4GUbH2hMWcDauPFJzmCZUAJ8L2bTkVXC2niOIxp8lk3iH/QX8kSrUxVZrOVw==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.4.tgz", + "integrity": "sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==", "cpu": [ "arm64" ], @@ -2844,9 +2751,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.3.tgz", - "integrity": "sha512-lj9ViATR1SsqycwFkJCtYfQTheBdvlWJqzqxwc9f2qrcVrQaF/gCuBRTiTolkRWS6KvNxSk4KHZWG7tDktLgjg==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.4.tgz", + "integrity": "sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==", "cpu": [ "arm64" ], @@ -2858,9 +2765,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.3.tgz", - "integrity": "sha512-+Dyo7O1KUmIsbzx1l+4V4tvEVnVQqMOIYtrxK7ncLSknl1xnMHLgn7gddJVrYPNZfEB8CIi3hK8gq8bDhb3h5A==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.4.tgz", + "integrity": "sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==", "cpu": [ "x64" ], @@ -2872,9 +2779,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.3.tgz", - "integrity": "sha512-u9Xg2FavYbD30g3DSfNhxgNrxhi6xVG4Y6i9Ur1C7xUuGDW3banRbXj+qgnIrwRN4KeJ396jchwy9bCIzbyBEQ==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.4.tgz", + "integrity": "sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==", "cpu": [ "arm64" ], @@ -2886,9 +2793,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.3.tgz", - "integrity": "sha512-5M8kyi/OX96wtD5qJR89a/3x5x8x5inXBZO04JWhkQb2JWavOWfjgkdvUqibGJeNNaz1/Z1PPza5/tAPXICI6A==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.4.tgz", + "integrity": "sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==", "cpu": [ "x64" ], @@ -2900,9 +2807,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.3.tgz", - "integrity": "sha512-IoerZJ4l1wRMopEHRKOO16e04iXRDyZFZnNZKrWeNquh5d6bucjezgd+OxG03mOMTnS1x7hilzb3uURPkJ0OfA==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.4.tgz", + "integrity": "sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==", "cpu": [ "arm" ], @@ -2914,9 +2821,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.3.tgz", - "integrity": "sha512-ZYdtqgHTDfvrJHSh3W22TvjWxwOgc3ThK/XjgcNGP2DIwFIPeAPNsQxrJO5XqleSlgDux2VAoWQ5iJrtaC1TbA==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.4.tgz", + "integrity": "sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==", "cpu": [ "arm" ], @@ -2928,9 +2835,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.3.tgz", - "integrity": "sha512-NcViG7A0YtuFDA6xWSgmFb6iPFzHlf5vcqb2p0lGEbT+gjrEEz8nC/EeDHvx6mnGXnGCC1SeVV+8u+smj0CeGQ==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.4.tgz", + "integrity": "sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==", "cpu": [ "arm64" ], @@ -2942,9 +2849,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.3.tgz", - "integrity": "sha512-d3pY7LWno6SYNXRm6Ebsq0DJGoiLXTb83AIPCXl9fmtIQs/rXoS8SJxxUNtFbJ5MiOvs+7y34np77+9l4nfFMw==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.4.tgz", + "integrity": "sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==", "cpu": [ "arm64" ], @@ -2956,9 +2863,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.3.tgz", - "integrity": "sha512-3y5GA0JkBuirLqmjwAKwB0keDlI6JfGYduMlJD/Rl7fvb4Ni8iKdQs1eiunMZJhwDWdCvrcqXRY++VEBbvk6Eg==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.4.tgz", + "integrity": "sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==", "cpu": [ "loong64" ], @@ -2970,9 +2877,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.3.tgz", - "integrity": "sha512-AUUH65a0p3Q0Yfm5oD2KVgzTKgwPyp9DSXc3UA7DtxhEb/WSPfbG4wqXeSN62OG5gSo18em4xv6dbfcUGXcagw==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.4.tgz", + "integrity": "sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==", "cpu": [ "ppc64" ], @@ -2984,9 +2891,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.3.tgz", - "integrity": "sha512-1makPhFFVBqZE+XFg3Dkq+IkQ7JvmUrwwqaYBL2CE+ZpxPaqkGaiWFEWVGyvTwZace6WLJHwjVh/+CXbKDGPmg==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.4.tgz", + "integrity": "sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==", "cpu": [ "riscv64" ], @@ -2998,9 +2905,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.3.tgz", - "integrity": "sha512-OOFJa28dxfl8kLOPMUOQBCO6z3X2SAfzIE276fwT52uXDWUS178KWq0pL7d6p1kz7pkzA0yQwtqL0dEPoVcRWg==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.4.tgz", + "integrity": "sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==", "cpu": [ "riscv64" ], @@ -3012,9 +2919,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.3.tgz", - "integrity": "sha512-jMdsML2VI5l+V7cKfZx3ak+SLlJ8fKvLJ0Eoa4b9/vCUrzXKgoKxvHqvJ/mkWhFiyp88nCkM5S2v6nIwRtPcgg==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.4.tgz", + "integrity": "sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==", "cpu": [ "s390x" ], @@ -3026,9 +2933,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.3.tgz", - "integrity": "sha512-tPgGd6bY2M2LJTA1uGq8fkSPK8ZLYjDjY+ZLK9WHncCnfIz29LIXIqUgzCR0hIefzy6Hpbe8Th5WOSwTM8E7LA==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz", + "integrity": "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==", "cpu": [ "x64" ], @@ -3040,9 +2947,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.3.tgz", - "integrity": "sha512-BCFkJjgk+WFzP+tcSMXq77ymAPIxsX9lFJWs+2JzuZTLtksJ2o5hvgTdIcZ5+oKzUDMwI0PfWzRBYAydAHF2Mw==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.4.tgz", + "integrity": "sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==", "cpu": [ "x64" ], @@ -3054,9 +2961,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.3.tgz", - "integrity": "sha512-KTD/EqjZF3yvRaWUJdD1cW+IQBk4fbQaHYJUmP8N4XoKFZilVL8cobFSTDnjTtxWJQ3JYaMgF4nObY/+nYkumA==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.4.tgz", + "integrity": "sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==", "cpu": [ "arm64" ], @@ -3068,9 +2975,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.3.tgz", - "integrity": "sha512-+zteHZdoUYLkyYKObGHieibUFLbttX2r+58l27XZauq0tcWYYuKUwY2wjeCN9oK1Um2YgH2ibd6cnX/wFD7DuA==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.4.tgz", + "integrity": "sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==", "cpu": [ "arm64" ], @@ -3082,9 +2989,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.3.tgz", - "integrity": "sha512-of1iHkTQSo3kr6dTIRX6t81uj/c/b15HXVsPcEElN5sS859qHrOepM5p9G41Hah+CTqSh2r8Bm56dL2z9UQQ7g==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.4.tgz", + "integrity": "sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==", "cpu": [ "ia32" ], @@ -3096,9 +3003,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.3.tgz", - "integrity": "sha512-s0hybmlHb56mWVZQj8ra9048/WZTPLILKxcvcq+8awSZmyiSUZjjem1AhU3Tf4ZKpYhK4mg36HtHDOe8QJS5PQ==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.4.tgz", + "integrity": "sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==", "cpu": [ "x64" ], @@ -3110,9 +3017,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.3.tgz", - "integrity": "sha512-zGIbEVVXVtauFgl3MRwGWEN36P5ZGenHRMgNw88X5wEhEBpq0XrMEZwOn07+ICrwM17XO5xfMZqh0OldCH5VTA==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.4.tgz", + "integrity": "sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==", "cpu": [ "x64" ], @@ -3884,6 +3791,54 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/gapi": { + "version": "0.0.47", + "resolved": "https://registry.npmjs.org/@types/gapi/-/gapi-0.0.47.tgz", + "integrity": "sha512-/ZsLuq6BffMgbKMtZyDZ8vwQvTyKhKQ1G2K6VyWCgtHHhfSSXbk4+4JwImZiTjWNXfI2q1ZStAwFFHSkNoTkHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/gapi.client": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/gapi.client/-/gapi.client-1.0.8.tgz", + "integrity": "sha512-qJQUmmumbYym3Amax0S8CVzuSngcXsC1fJdwRS2zeW5lM63zXkw4wJFP+bG0jzgi0R6EsJKoHnGNVTDbOyG1ng==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/gapi.client.discovery-v1": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@types/gapi.client.discovery-v1/-/gapi.client.discovery-v1-0.0.4.tgz", + "integrity": "sha512-uevhRumNE65F5mf2gABLaReOmbFSXONuzFZjNR3dYv6BmkHg+wciubHrfBAsp3554zNo3Dcg6dUAlwMqQfpwjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@maxim_mazurok/gapi.client.discovery-v1": "latest" + } + }, + "node_modules/@types/gapi.client.drive-v3": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@types/gapi.client.drive-v3/-/gapi.client.drive-v3-0.0.5.tgz", + "integrity": "sha512-yYBxiqMqJVBg4bns4Q28+f2XdJnd3tVA9dxQX1lXMVmzT2B+pZdyCi1u9HLwGveVlookSsAXuqfLfS9KO6MF6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@maxim_mazurok/gapi.client.drive-v3": "latest" + } + }, + "node_modules/@types/google.accounts": { + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/@types/google.accounts/-/google.accounts-0.0.18.tgz", + "integrity": "sha512-yHaPznll97ZnMJlPABHyeiIlLn3u6gQaUjA5k/O9lrrpgFB9VT10CKPLuKM0qTHMl50uXpW5sIcG+utm8jMOHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/google.picker": { + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/google.picker/-/google.picker-0.0.51.tgz", + "integrity": "sha512-z6o2J4PQTcXvlW1rtgQx65d5uEF+rMI1hzrnazKQxBONdEuYAr4AeOSH2KZy12WHPmqMX+aWYyfcZ0uktBBhhA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/http-cache-semantics": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", @@ -3898,13 +3853,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.6.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.6.2.tgz", - "integrity": "sha512-d2L25Y4j+W3ZlNAeMKcy7yDsK425ibcAOO2t7aPTz6gNMH0z2GThtwENCDc0d/Pw9wgyRqE5Px1wkV7naz8ang==", + "version": "24.7.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.2.tgz", + "integrity": "sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.13.0" + "undici-types": "~7.14.0" } }, "node_modules/@types/parse-json": { @@ -3920,18 +3875,18 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.2.0", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.0.tgz", - "integrity": "sha512-1LOH8xovvsKsCBq1wnT4ntDUdCJKmnEakhsuoUSy6ExlHCkGP2hqnatagYTgFk6oeL0VU31u7SNjunPN+GchtA==", + "version": "19.2.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", + "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "license": "MIT", "dependencies": { "csstype": "^3.0.2" } }, "node_modules/@types/react-dom": { - "version": "19.2.0", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.0.tgz", - "integrity": "sha512-brtBs0MnE9SMx7px208g39lRmC5uHZs96caOJfTjFcYSLHNamvaSMfJNagChVNkup2SdtOxKX1FDBkRSJe1ZAg==", + "version": "19.2.2", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.2.tgz", + "integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==", "dev": true, "license": "MIT", "peerDependencies": { @@ -3948,17 +3903,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.45.0.tgz", - "integrity": "sha512-HC3y9CVuevvWCl/oyZuI47dOeDF9ztdMEfMH8/DW/Mhwa9cCLnK1oD7JoTVGW/u7kFzNZUKUoyJEqkaJh5y3Wg==", + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.1.tgz", + "integrity": "sha512-rUsLh8PXmBjdiPY+Emjz9NX2yHvhS11v0SR6xNJkm5GM1MO9ea/1GoDKlHHZGrOJclL/cZ2i/vRUYVtjRhrHVQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.45.0", - "@typescript-eslint/type-utils": "8.45.0", - "@typescript-eslint/utils": "8.45.0", - "@typescript-eslint/visitor-keys": "8.45.0", + "@typescript-eslint/scope-manager": "8.46.1", + "@typescript-eslint/type-utils": "8.46.1", + "@typescript-eslint/utils": "8.46.1", + "@typescript-eslint/visitor-keys": "8.46.1", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -3972,22 +3927,22 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.45.0", + "@typescript-eslint/parser": "^8.46.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.45.0.tgz", - "integrity": "sha512-TGf22kon8KW+DeKaUmOibKWktRY8b2NSAZNdtWh798COm1NWx8+xJ6iFBtk3IvLdv6+LGLJLRlyhrhEDZWargQ==", + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.1.tgz", + "integrity": "sha512-6JSSaBZmsKvEkbRUkf7Zj7dru/8ZCrJxAqArcLaVMee5907JdtEbKGsZ7zNiIm/UAkpGUkaSMZEXShnN2D1HZA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.45.0", - "@typescript-eslint/types": "8.45.0", - "@typescript-eslint/typescript-estree": "8.45.0", - "@typescript-eslint/visitor-keys": "8.45.0", + "@typescript-eslint/scope-manager": "8.46.1", + "@typescript-eslint/types": "8.46.1", + "@typescript-eslint/typescript-estree": "8.46.1", + "@typescript-eslint/visitor-keys": "8.46.1", "debug": "^4.3.4" }, "engines": { @@ -4003,14 +3958,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.45.0.tgz", - "integrity": "sha512-3pcVHwMG/iA8afdGLMuTibGR7pDsn9RjDev6CCB+naRsSYs2pns5QbinF4Xqw6YC/Sj3lMrm/Im0eMfaa61WUg==", + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.1.tgz", + "integrity": "sha512-FOIaFVMHzRskXr5J4Jp8lFVV0gz5ngv3RHmn+E4HYxSJ3DgDzU7fVI1/M7Ijh1zf6S7HIoaIOtln1H5y8V+9Zg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.45.0", - "@typescript-eslint/types": "^8.45.0", + "@typescript-eslint/tsconfig-utils": "^8.46.1", + "@typescript-eslint/types": "^8.46.1", "debug": "^4.3.4" }, "engines": { @@ -4025,14 +3980,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.45.0.tgz", - "integrity": "sha512-clmm8XSNj/1dGvJeO6VGH7EUSeA0FMs+5au/u3lrA3KfG8iJ4u8ym9/j2tTEoacAffdW1TVUzXO30W1JTJS7dA==", + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.1.tgz", + "integrity": "sha512-weL9Gg3/5F0pVQKiF8eOXFZp8emqWzZsOJuWRUNtHT+UNV2xSJegmpCNQHy37aEQIbToTq7RHKhWvOsmbM680A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.45.0", - "@typescript-eslint/visitor-keys": "8.45.0" + "@typescript-eslint/types": "8.46.1", + "@typescript-eslint/visitor-keys": "8.46.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4043,9 +3998,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.45.0.tgz", - "integrity": "sha512-aFdr+c37sc+jqNMGhH+ajxPXwjv9UtFZk79k8pLoJ6p4y0snmYpPA52GuWHgt2ZF4gRRW6odsEj41uZLojDt5w==", + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.1.tgz", + "integrity": "sha512-X88+J/CwFvlJB+mK09VFqx5FE4H5cXD+H/Bdza2aEWkSb8hnWIQorNcscRl4IEo1Cz9VI/+/r/jnGWkbWPx54g==", "dev": true, "license": "MIT", "engines": { @@ -4060,15 +4015,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.45.0.tgz", - "integrity": "sha512-bpjepLlHceKgyMEPglAeULX1vixJDgaKocp0RVJ5u4wLJIMNuKtUXIczpJCPcn2waII0yuvks/5m5/h3ZQKs0A==", + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.1.tgz", + "integrity": "sha512-+BlmiHIiqufBxkVnOtFwjah/vrkF4MtKKvpXrKSPLCkCtAp8H01/VV43sfqA98Od7nJpDcFnkwgyfQbOG0AMvw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.45.0", - "@typescript-eslint/typescript-estree": "8.45.0", - "@typescript-eslint/utils": "8.45.0", + "@typescript-eslint/types": "8.46.1", + "@typescript-eslint/typescript-estree": "8.46.1", + "@typescript-eslint/utils": "8.46.1", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -4085,9 +4040,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.45.0.tgz", - "integrity": "sha512-WugXLuOIq67BMgQInIxxnsSyRLFxdkJEJu8r4ngLR56q/4Q5LrbfkFRH27vMTjxEK8Pyz7QfzuZe/G15qQnVRA==", + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.1.tgz", + "integrity": "sha512-C+soprGBHwWBdkDpbaRC4paGBrkIXxVlNohadL5o0kfhsXqOC6GYH2S/Obmig+I0HTDl8wMaRySwrfrXVP8/pQ==", "dev": true, "license": "MIT", "engines": { @@ -4099,16 +4054,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.45.0.tgz", - "integrity": "sha512-GfE1NfVbLam6XQ0LcERKwdTTPlLvHvXXhOeUGC1OXi4eQBoyy1iVsW+uzJ/J9jtCz6/7GCQ9MtrQ0fml/jWCnA==", + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.1.tgz", + "integrity": "sha512-uIifjT4s8cQKFQ8ZBXXyoUODtRoAd7F7+G8MKmtzj17+1UbdzFl52AzRyZRyKqPHhgzvXunnSckVu36flGy8cg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.45.0", - "@typescript-eslint/tsconfig-utils": "8.45.0", - "@typescript-eslint/types": "8.45.0", - "@typescript-eslint/visitor-keys": "8.45.0", + "@typescript-eslint/project-service": "8.46.1", + "@typescript-eslint/tsconfig-utils": "8.46.1", + "@typescript-eslint/types": "8.46.1", + "@typescript-eslint/visitor-keys": "8.46.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -4128,16 +4083,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.45.0.tgz", - "integrity": "sha512-bxi1ht+tLYg4+XV2knz/F7RVhU0k6VrSMc9sb8DQ6fyCTrGQLHfo7lDtN0QJjZjKkLA2ThrKuCdHEvLReqtIGg==", + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.1.tgz", + "integrity": "sha512-vkYUy6LdZS7q1v/Gxb2Zs7zziuXN0wxqsetJdeZdRe/f5dwJFglmuvZBfTUivCtjH725C1jWCDfpadadD95EDQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.45.0", - "@typescript-eslint/types": "8.45.0", - "@typescript-eslint/typescript-estree": "8.45.0" + "@typescript-eslint/scope-manager": "8.46.1", + "@typescript-eslint/types": "8.46.1", + "@typescript-eslint/typescript-estree": "8.46.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4152,13 +4107,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.45.0.tgz", - "integrity": "sha512-qsaFBA3e09MIDAGFUrTk+dzqtfv1XPVz8t8d1f0ybTzrCY7BKiMC5cjrl1O/P7UmHsNyW90EYSkU/ZWpmXelag==", + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.1.tgz", + "integrity": "sha512-ptkmIf2iDkNUjdeu2bQqhFPV1m6qTnFFjg7PPDjxKWaMaP0Z6I9l30Jr3g5QqbZGdw8YdYvLp+XnqnWWZOg/NA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/types": "8.46.1", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -4780,13 +4735,13 @@ } }, "node_modules/ast-v8-to-istanbul": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.5.tgz", - "integrity": "sha512-9SdXjNheSiE8bALAQCQQuT6fgQaoxJh7IRYrRGZ8/9nv8WhJeC1aXAwN8TbaOssGOukUvyvnkgD9+Yuykvl1aA==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.6.tgz", + "integrity": "sha512-9tx1z/7OF/a8EdYL3FKoBhxLf3h3D8fXvuSj0HknsVeli2HE40qbNZxyFhMtnydaRiamwFu9zhb+BsJ5tVPehQ==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.30", + "@jridgewell/trace-mapping": "^0.3.31", "estree-walker": "^3.0.3", "js-tokens": "^9.0.1" } @@ -4931,9 +4886,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.10", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.10.tgz", - "integrity": "sha512-uLfgBi+7IBNay8ECBO2mVMGZAc1VgZWEChxm4lv+TobGdG82LnXMjuNGo/BSSZZL4UmkWhxEHP2f5ziLNwGWMA==", + "version": "2.8.16", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.16.tgz", + "integrity": "sha512-OMu3BGQ4E7P1ErFsIPpbJh0qvDudM/UuJeHgkAvfWe+0HFJCXh+t/l8L6fVLR55RI/UbKrVLnAXZSVwd9ysWYw==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.js" @@ -5096,23 +5051,47 @@ } }, "node_modules/cacheable-request": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-12.0.1.tgz", - "integrity": "sha512-Yo9wGIQUaAfIbk+qY0X4cDQgCosecfBe3V9NSyeY4qPC2SAkbCS4Xj79VP8WOzitpJUZKc/wsRCYF5ariDIwkg==", + "version": "13.0.12", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-13.0.12.tgz", + "integrity": "sha512-qqK/etGeI/9DV5yRkO50ApDTjip9UXPml1NHYJksUAw15yMLOf8VUO1/8bu4P8birOCqR+hYQ/nh1Lezc8sZrA==", "license": "MIT", "dependencies": { "@types/http-cache-semantics": "^4.0.4", "get-stream": "^9.0.1", - "http-cache-semantics": "^4.1.1", - "keyv": "^4.5.4", + "http-cache-semantics": "^4.2.0", + "keyv": "^5.5.3", "mimic-response": "^4.0.0", - "normalize-url": "^8.0.1", + "normalize-url": "^8.1.0", "responselike": "^3.0.0" }, "engines": { "node": ">=18" } }, + "node_modules/cacheable-request/node_modules/keyv": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.5.3.tgz", + "integrity": "sha512-h0Un1ieD+HUrzBH6dJXhod3ifSghk5Hw/2Y4/KHBziPlZecrFyE9YOTPU6eOs0V9pYl8gOs86fkr/KN8lUX39A==", + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, + "node_modules/cacheable-request/node_modules/responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", + "license": "MIT", + "dependencies": { + "lowercase-keys": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -5182,9 +5161,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001746", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001746.tgz", - "integrity": "sha512-eA7Ys/DGw+pnkWWSE/id29f2IcPHVoE8wxtvE5JdvD2V28VTDPy1yEeo11Guz0sJ4ZeGRcm3uaTcAqK1LXaphA==", + "version": "1.0.30001750", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001750.tgz", + "integrity": "sha512-cuom0g5sdX6rw00qOoLNSFCJ9/mYIsuSOA+yzpDw8eopiFqcVwQvZHqov0vmEighRxX++cfC0Vg1G+1Iy/mSpQ==", "funding": [ { "type": "opencollective", @@ -5431,9 +5410,9 @@ } }, "node_modules/core-js": { - "version": "3.45.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.45.1.tgz", - "integrity": "sha512-L4NPsJlCfZsPeXukyzHFlg/i7IIVwHSItR0wg0FLNqYClJ4MQYTYLbC7EkjKYRLZF2iof2MUgN0EGy7MdQFChg==", + "version": "3.46.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.46.0.tgz", + "integrity": "sha512-vDMm9B0xnqqZ8uSBpZ8sNtRtOdmfShrvT6h2TuQGLs0Is+cR0DYbj/KWP6ALVNbWPpqA/qPLoOuppJN07humpA==", "hasInstallScript": true, "license": "MIT", "funding": { @@ -5831,9 +5810,9 @@ } }, "node_modules/detect-libc": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.1.tgz", - "integrity": "sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "license": "Apache-2.0", "engines": { "node": ">=8" @@ -6049,9 +6028,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.228", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.228.tgz", - "integrity": "sha512-nxkiyuqAn4MJ1QbobwqJILiDtu/jk14hEAWaMiJmNPh1Z+jqoFlBFZjdXwLWGeVSeu9hGLg6+2G9yJaW8rBIFA==", + "version": "1.5.237", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.237.tgz", + "integrity": "sha512-icUt1NvfhGLar5lSWH3tHNzablaA5js3HVHacQimfP8ViEBOQv+L7DKEuHdbTZ0SKCO1ogTJTIL1Gwk9S6Qvcg==", "license": "ISC" }, "node_modules/emoji-regex": { @@ -6286,9 +6265,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", - "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz", + "integrity": "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -6299,32 +6278,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.10", - "@esbuild/android-arm": "0.25.10", - "@esbuild/android-arm64": "0.25.10", - "@esbuild/android-x64": "0.25.10", - "@esbuild/darwin-arm64": "0.25.10", - "@esbuild/darwin-x64": "0.25.10", - "@esbuild/freebsd-arm64": "0.25.10", - "@esbuild/freebsd-x64": "0.25.10", - "@esbuild/linux-arm": "0.25.10", - "@esbuild/linux-arm64": "0.25.10", - "@esbuild/linux-ia32": "0.25.10", - "@esbuild/linux-loong64": "0.25.10", - "@esbuild/linux-mips64el": "0.25.10", - "@esbuild/linux-ppc64": "0.25.10", - "@esbuild/linux-riscv64": "0.25.10", - "@esbuild/linux-s390x": "0.25.10", - "@esbuild/linux-x64": "0.25.10", - "@esbuild/netbsd-arm64": "0.25.10", - "@esbuild/netbsd-x64": "0.25.10", - "@esbuild/openbsd-arm64": "0.25.10", - "@esbuild/openbsd-x64": "0.25.10", - "@esbuild/openharmony-arm64": "0.25.10", - "@esbuild/sunos-x64": "0.25.10", - "@esbuild/win32-arm64": "0.25.10", - "@esbuild/win32-ia32": "0.25.10", - "@esbuild/win32-x64": "0.25.10" + "@esbuild/aix-ppc64": "0.25.11", + "@esbuild/android-arm": "0.25.11", + "@esbuild/android-arm64": "0.25.11", + "@esbuild/android-x64": "0.25.11", + "@esbuild/darwin-arm64": "0.25.11", + "@esbuild/darwin-x64": "0.25.11", + "@esbuild/freebsd-arm64": "0.25.11", + "@esbuild/freebsd-x64": "0.25.11", + "@esbuild/linux-arm": "0.25.11", + "@esbuild/linux-arm64": "0.25.11", + "@esbuild/linux-ia32": "0.25.11", + "@esbuild/linux-loong64": "0.25.11", + "@esbuild/linux-mips64el": "0.25.11", + "@esbuild/linux-ppc64": "0.25.11", + "@esbuild/linux-riscv64": "0.25.11", + "@esbuild/linux-s390x": "0.25.11", + "@esbuild/linux-x64": "0.25.11", + "@esbuild/netbsd-arm64": "0.25.11", + "@esbuild/netbsd-x64": "0.25.11", + "@esbuild/openbsd-arm64": "0.25.11", + "@esbuild/openbsd-x64": "0.25.11", + "@esbuild/openharmony-arm64": "0.25.11", + "@esbuild/sunos-x64": "0.25.11", + "@esbuild/win32-arm64": "0.25.11", + "@esbuild/win32-ia32": "0.25.11", + "@esbuild/win32-x64": "0.25.11" } }, "node_modules/escalade": { @@ -6382,20 +6361,20 @@ } }, "node_modules/eslint": { - "version": "9.36.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.36.0.tgz", - "integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==", + "version": "9.37.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.37.0.tgz", + "integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", - "@eslint/config-helpers": "^0.3.1", - "@eslint/core": "^0.15.2", + "@eslint/config-helpers": "^0.4.0", + "@eslint/core": "^0.16.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.36.0", - "@eslint/plugin-kit": "^0.3.5", + "@eslint/js": "9.37.0", + "@eslint/plugin-kit": "^0.4.0", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -6476,18 +6455,17 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-6.1.0.tgz", - "integrity": "sha512-72mucw/WLzEqGvL2vwE6fWR6geO6UbmDjz3eAb3pezxTpFzgbfyUeFKzmZKr9LhwUWMXfTVh1g0rKEJoyKNdoA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.0.tgz", + "integrity": "sha512-fNXaOwvKwq2+pXiRpXc825Vd63+KM4DLL40Rtlycb8m7fYpp6efrTp1sa6ZbP/Ap58K2bEKFXRmhURE+CJAQWw==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", - "@babel/plugin-proposal-private-methods": "^7.18.6", "hermes-parser": "^0.25.1", - "zod": "^3.22.4", - "zod-validation-error": "^3.0.3" + "zod": "^3.22.4 || ^4.0.0", + "zod-validation-error": "^3.0.3 || ^4.0.0" }, "engines": { "node": ">=18" @@ -7395,21 +7373,22 @@ } }, "node_modules/got": { - "version": "14.4.9", - "resolved": "https://registry.npmjs.org/got/-/got-14.4.9.tgz", - "integrity": "sha512-Dbu075Jwm3QwNCIoCenqkqY8l2gd7e/TanuhMbzZIEsb1mpAneImSusKhZ+XdqqC3S91SDV/1SdWpGXKAlm8tA==", + "version": "14.5.0", + "resolved": "https://registry.npmjs.org/got/-/got-14.5.0.tgz", + "integrity": "sha512-rZ8+ZMYz9yeTFmFtOiwf/Trcli8gdqgqAz0kmkEOWcT7C3BFG3Z+dLQu0y8je7RogG7Wp1obnu5GerwCaccftw==", "license": "MIT", "dependencies": { "@sindresorhus/is": "^7.0.1", "@szmarczak/http-timer": "^5.0.1", "cacheable-lookup": "^7.0.0", - "cacheable-request": "^12.0.1", + "cacheable-request": "^13.0.12", "decompress-response": "^6.0.0", "form-data-encoder": "^4.0.2", "http2-wrapper": "^2.2.1", + "keyv": "^5.5.3", "lowercase-keys": "^3.0.0", "p-cancelable": "^4.0.1", - "responselike": "^3.0.0", + "responselike": "^4.0.2", "type-fest": "^4.26.1" }, "engines": { @@ -7419,6 +7398,15 @@ "url": "https://github.com/sindresorhus/got?sponsor=1" } }, + "node_modules/got/node_modules/keyv": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.5.3.tgz", + "integrity": "sha512-h0Un1ieD+HUrzBH6dJXhod3ifSghk5Hw/2Y4/KHBziPlZecrFyE9YOTPU6eOs0V9pYl8gOs86fkr/KN8lUX39A==", + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -7648,9 +7636,9 @@ } }, "node_modules/i18next": { - "version": "25.5.3", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.5.3.tgz", - "integrity": "sha512-joFqorDeQ6YpIXni944upwnuHBf5IoPMuqAchGVeQLdWC2JOjxgM9V8UGLhNIIH/Q8QleRxIi0BSRQehSrDLcg==", + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.6.0.tgz", + "integrity": "sha512-tTn8fLrwBYtnclpL5aPXK/tAYBLWVvoHM1zdfXoRNLcI+RvtMsoZRV98ePlaW3khHYKuNh/Q65W/+NVFUeIwVw==", "funding": [ { "type": "individual", @@ -8511,6 +8499,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, "license": "MIT" }, "node_modules/json-parse-even-better-errors": { @@ -8591,6 +8580,7 @@ "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, "license": "MIT", "dependencies": { "json-buffer": "3.0.1" @@ -9123,13 +9113,13 @@ } }, "node_modules/lru-cache": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", - "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "license": "ISC", - "engines": { - "node": "20 || >=22" + "dependencies": { + "yallist": "^3.0.2" } }, "node_modules/lz-string": { @@ -9538,9 +9528,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.21", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz", - "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==", + "version": "2.0.23", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.23.tgz", + "integrity": "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==", "license": "MIT" }, "node_modules/node-source-walk": { @@ -9905,9 +9895,9 @@ "license": "BlueOak-1.0.0" }, "node_modules/package-manager-detector": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.3.0.tgz", - "integrity": "sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.4.1.tgz", + "integrity": "sha512-dSMiVLBEA4XaNJ0PRb4N5cV/SEP4BWrWZKBmfF+OUm2pQTiZ6DDkKeWaltwu3JRhLoy59ayIkJ00cx9K9CaYTg==", "dev": true, "license": "MIT" }, @@ -10069,15 +10059,15 @@ } }, "node_modules/pdfjs-dist": { - "version": "5.4.149", - "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.4.149.tgz", - "integrity": "sha512-Xe8/1FMJEQPUVSti25AlDpwpUm2QAVmNOpFP0SIahaPIOKBKICaefbzogLdwey3XGGoaP4Lb9wqiw2e9Jqp0LA==", + "version": "5.4.296", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.4.296.tgz", + "integrity": "sha512-DlOzet0HO7OEnmUmB6wWGJrrdvbyJKftI1bhMitK7O2N8W2gc757yyYBbINy9IDafXAV9wmKr9t7xsTaNKRG5Q==", "license": "Apache-2.0", "engines": { "node": ">=20.16.0 || >=22.3.0" }, "optionalDependencies": { - "@napi-rs/canvas": "^0.1.77" + "@napi-rs/canvas": "^0.1.80" } }, "node_modules/picocolors": { @@ -10122,13 +10112,13 @@ } }, "node_modules/playwright": { - "version": "1.55.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.1.tgz", - "integrity": "sha512-cJW4Xd/G3v5ovXtJJ52MAOclqeac9S/aGGgRzLabuF8TnIb6xHvMzKIa6JmrRzUkeXJgfL1MhukP0NK6l39h3A==", + "version": "1.56.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.0.tgz", + "integrity": "sha512-X5Q1b8lOdWIE4KAoHpW3SE8HvUB+ZZsUoN64ZhjnN8dOb1UpujxBtENGiZFE+9F/yhzJwYa+ca3u43FeLbboHA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.55.1" + "playwright-core": "1.56.0" }, "bin": { "playwright": "cli.js" @@ -10141,9 +10131,9 @@ } }, "node_modules/playwright-core": { - "version": "1.55.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.1.tgz", - "integrity": "sha512-Z6Mh9mkwX+zxSlHqdr5AOcJnfp+xUWLCt9uKV18fhzA8eyxUd8NUWzAjxUh55RZKSYwDGX0cfaySdhZJGMoJ+w==", + "version": "1.56.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.0.tgz", + "integrity": "sha512-1SXl7pMfemAMSDn5rkPeZljxOCYAmQnYLBTExuh6E8USHXGSX3dx6lYZN/xPpTz1vimXmPA9CDnILvmJaB8aSQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -10448,12 +10438,12 @@ } }, "node_modules/posthog-js": { - "version": "1.269.1", - "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.269.1.tgz", - "integrity": "sha512-4GrcKQ89uLwBGNtHrhq5QRCzHmPPCxE6i0cDsk4mFMls9iZcsdTpITBNepHbP0Bvn62HZUVAiqGeRlOpy8XeCA==", + "version": "1.275.3", + "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.275.3.tgz", + "integrity": "sha512-LitwVprl0Q8p0fN4O4ThvlOuO6r+TBzLfkGbSyI5tR/YhlWzX3yFf4KKHPXJgxge99sxKa0fuVKNzcspYsDzcg==", "license": "SEE LICENSE IN LICENSE", "dependencies": { - "@posthog/core": "1.2.2", + "@posthog/core": "1.3.0", "core-js": "^3.38.1", "fflate": "^0.4.8", "preact": "^10.19.3", @@ -10871,9 +10861,9 @@ "license": "0BSD" }, "node_modules/react-router": { - "version": "7.9.3", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.3.tgz", - "integrity": "sha512-4o2iWCFIwhI/eYAIL43+cjORXYn/aRQPgtFRRZb3VzoyQ5Uej0Bmqj7437L97N9NJW4wnicSwLOLS+yCXfAPgg==", + "version": "7.9.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.4.tgz", + "integrity": "sha512-SD3G8HKviFHg9xj7dNODUKDFgpG4xqD5nhyd0mYoB5iISepuZAvzSr8ywxgxKJ52yRzf/HWtVHc9AWwoTbljvA==", "license": "MIT", "dependencies": { "cookie": "^1.0.1", @@ -10893,12 +10883,12 @@ } }, "node_modules/react-router-dom": { - "version": "7.9.3", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.9.3.tgz", - "integrity": "sha512-1QSbA0TGGFKTAc/aWjpfW/zoEukYfU4dc1dLkT/vvf54JoGMkW+fNA+3oyo2gWVW1GM7BxjJVHz5GnPJv40rvg==", + "version": "7.9.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.9.4.tgz", + "integrity": "sha512-f30P6bIkmYvnHHa5Gcu65deIXoA2+r3Eb6PJIAddvsT9aGlchMatJ51GgpU470aSqRRbFX22T70yQNUGuW3DfA==", "license": "MIT", "dependencies": { - "react-router": "7.9.3" + "react-router": "7.9.4" }, "engines": { "node": ">=20.0.0" @@ -11216,15 +11206,15 @@ } }, "node_modules/responselike": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", - "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-4.0.2.tgz", + "integrity": "sha512-cGk8IbWEAnaCpdAt1BHzJ3Ahz5ewDJa0KseTsE3qIRMJ3C698W8psM7byCeWVpd/Ha7FUYzuRVzXoKoM6nRUbA==", "license": "MIT", "dependencies": { "lowercase-keys": "^3.0.0" }, "engines": { - "node": ">=14.16" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -11256,9 +11246,9 @@ } }, "node_modules/rollup": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.3.tgz", - "integrity": "sha512-RIDh866U8agLgiIcdpB+COKnlCreHJLfIhWC3LVflku5YHfpnsIKigRZeFfMfCc4dVcqNVfQQ5gO/afOck064A==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz", + "integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==", "dev": true, "license": "MIT", "dependencies": { @@ -11272,28 +11262,28 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.52.3", - "@rollup/rollup-android-arm64": "4.52.3", - "@rollup/rollup-darwin-arm64": "4.52.3", - "@rollup/rollup-darwin-x64": "4.52.3", - "@rollup/rollup-freebsd-arm64": "4.52.3", - "@rollup/rollup-freebsd-x64": "4.52.3", - "@rollup/rollup-linux-arm-gnueabihf": "4.52.3", - "@rollup/rollup-linux-arm-musleabihf": "4.52.3", - "@rollup/rollup-linux-arm64-gnu": "4.52.3", - "@rollup/rollup-linux-arm64-musl": "4.52.3", - "@rollup/rollup-linux-loong64-gnu": "4.52.3", - "@rollup/rollup-linux-ppc64-gnu": "4.52.3", - "@rollup/rollup-linux-riscv64-gnu": "4.52.3", - "@rollup/rollup-linux-riscv64-musl": "4.52.3", - "@rollup/rollup-linux-s390x-gnu": "4.52.3", - "@rollup/rollup-linux-x64-gnu": "4.52.3", - "@rollup/rollup-linux-x64-musl": "4.52.3", - "@rollup/rollup-openharmony-arm64": "4.52.3", - "@rollup/rollup-win32-arm64-msvc": "4.52.3", - "@rollup/rollup-win32-ia32-msvc": "4.52.3", - "@rollup/rollup-win32-x64-gnu": "4.52.3", - "@rollup/rollup-win32-x64-msvc": "4.52.3", + "@rollup/rollup-android-arm-eabi": "4.52.4", + "@rollup/rollup-android-arm64": "4.52.4", + "@rollup/rollup-darwin-arm64": "4.52.4", + "@rollup/rollup-darwin-x64": "4.52.4", + "@rollup/rollup-freebsd-arm64": "4.52.4", + "@rollup/rollup-freebsd-x64": "4.52.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.4", + "@rollup/rollup-linux-arm-musleabihf": "4.52.4", + "@rollup/rollup-linux-arm64-gnu": "4.52.4", + "@rollup/rollup-linux-arm64-musl": "4.52.4", + "@rollup/rollup-linux-loong64-gnu": "4.52.4", + "@rollup/rollup-linux-ppc64-gnu": "4.52.4", + "@rollup/rollup-linux-riscv64-gnu": "4.52.4", + "@rollup/rollup-linux-riscv64-musl": "4.52.4", + "@rollup/rollup-linux-s390x-gnu": "4.52.4", + "@rollup/rollup-linux-x64-gnu": "4.52.4", + "@rollup/rollup-linux-x64-musl": "4.52.4", + "@rollup/rollup-openharmony-arm64": "4.52.4", + "@rollup/rollup-win32-arm64-msvc": "4.52.4", + "@rollup/rollup-win32-ia32-msvc": "4.52.4", + "@rollup/rollup-win32-x64-gnu": "4.52.4", + "@rollup/rollup-win32-x64-msvc": "4.52.4", "fsevents": "~2.3.2" } }, @@ -11457,9 +11447,9 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -11659,6 +11649,12 @@ "dev": true, "license": "ISC" }, + "node_modules/signature_pad": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/signature_pad/-/signature_pad-5.1.1.tgz", + "integrity": "sha512-BT5JJygS5BS0oV+tffPRorIud6q17bM7v/1LdQwd0o6mTqGoI25yY1NjSL99OqkekWltS4uon6p52Y8j1Zqu7g==", + "license": "MIT" + }, "node_modules/slash": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", @@ -11788,9 +11784,9 @@ "license": "MIT" }, "node_modules/std-env": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", - "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", "dev": true, "license": "MIT" }, @@ -12195,6 +12191,15 @@ "node": ">=18" } }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/test-exclude": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", @@ -12337,22 +12342,22 @@ } }, "node_modules/tldts": { - "version": "7.0.16", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.16.tgz", - "integrity": "sha512-5bdPHSwbKTeHmXrgecID4Ljff8rQjv7g8zKQPkCozRo2HWWni+p310FSn5ImI+9kWw9kK4lzOB5q/a6iv0IJsw==", + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.17.tgz", + "integrity": "sha512-Y1KQBgDd/NUc+LfOtKS6mNsC9CCaH+m2P1RoIZy7RAPo3C3/t8X45+zgut31cRZtZ3xKPjfn3TkGTrctC2TQIQ==", "dev": true, "license": "MIT", "dependencies": { - "tldts-core": "^7.0.16" + "tldts-core": "^7.0.17" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "7.0.16", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.16.tgz", - "integrity": "sha512-XHhPmHxphLi+LGbH0G/O7dmUH9V65OY20R7vH8gETHsp5AZCjBk9l8sqmRKLaGOxnETU7XNSDUPtewAy/K6jbA==", + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.17.tgz", + "integrity": "sha512-DieYoGrP78PWKsrXr8MZwtQ7GLCUeLxihtjC1jZsW1DnvSMdKPitJSe8OSYDM2u5H6g3kWJZpePqkp43TfLh0g==", "dev": true, "license": "MIT" }, @@ -12583,16 +12588,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.45.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.45.0.tgz", - "integrity": "sha512-qzDmZw/Z5beNLUrXfd0HIW6MzIaAV5WNDxmMs9/3ojGOpYavofgNAAD/nC6tGV2PczIi0iw8vot2eAe/sBn7zg==", + "version": "8.46.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.46.1.tgz", + "integrity": "sha512-VHgijW803JafdSsDO8I761r3SHrgk4T00IdyQ+/UsthtgPRsBWQLqoSxOolxTpxRKi1kGXK0bSz4CoAc9ObqJA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.45.0", - "@typescript-eslint/parser": "8.45.0", - "@typescript-eslint/typescript-estree": "8.45.0", - "@typescript-eslint/utils": "8.45.0" + "@typescript-eslint/eslint-plugin": "8.46.1", + "@typescript-eslint/parser": "8.46.1", + "@typescript-eslint/typescript-estree": "8.46.1", + "@typescript-eslint/utils": "8.46.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -12633,9 +12638,9 @@ } }, "node_modules/undici-types": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.13.0.tgz", - "integrity": "sha512-Ov2Rr9Sx+fRgagJ5AX0qvItZG/JKKoBRAVITs1zk7IqZGTJUwgUr7qoYBpWwakpWilTZFM98rG/AFRocu10iIQ==", + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz", + "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==", "dev": true, "license": "MIT" }, @@ -12835,9 +12840,9 @@ "license": "MIT" }, "node_modules/vite": { - "version": "7.1.8", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.8.tgz", - "integrity": "sha512-oBXvfSHEOL8jF+R9Am7h59Up07kVVGH1NrFGFoEG6bPDZP3tGpQhvkBpy5x7U6+E6wZCu9OihsWgJqDbQIm8LQ==", + "version": "7.1.10", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.10.tgz", + "integrity": "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA==", "dev": true, "license": "MIT", "dependencies": { @@ -13421,13 +13426,11 @@ } }, "node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" }, "node_modules/yaml": { "version": "2.8.1", @@ -13485,9 +13488,9 @@ } }, "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", + "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", "dev": true, "license": "MIT", "funding": { @@ -13495,9 +13498,9 @@ } }, "node_modules/zod-validation-error": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-3.5.3.tgz", - "integrity": "sha512-OT5Y8lbUadqVZCsnyFaTQ4/O2mys4tj7PqhdbBCp7McPwvIEKfPtdA6QfPeFQK2/Rz5LgwmAXRJTugBNBi0btw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", "dev": true, "license": "MIT", "engines": { diff --git a/frontend/package.json b/frontend/package.json index 5c88b5de8c..31026774f0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -50,6 +50,7 @@ "react-dom": "^19.1.1", "react-i18next": "^15.7.3", "react-router-dom": "^7.9.1", + "signature_pad": "^5.0.4", "tailwindcss": "^4.1.13", "web-vitals": "^5.1.0" }, @@ -97,7 +98,7 @@ ] }, "devDependencies": { - "@eslint/js": "^9.36.0", + "@eslint/js": "^9.37.0", "@iconify-json/material-symbols": "^1.2.37", "@iconify/utils": "^3.0.2", "@playwright/test": "^1.55.0", @@ -106,16 +107,20 @@ "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", "@tsconfig/vite-react": "^7.0.1", + "@types/gapi": "^0.0.47", + "@types/gapi.client.drive-v3": "^0.0.5", + "@types/google.accounts": "^0.0.18", + "@types/google.picker": "^0.0.51", "@types/node": "^24.5.2", "@types/react": "^19.1.13", "@types/react-dom": "^19.1.9", - "@typescript-eslint/eslint-plugin": "^8.45.0", - "@typescript-eslint/parser": "^8.45.0", + "@typescript-eslint/eslint-plugin": "^8.46.1", + "@typescript-eslint/parser": "^8.46.1", "@vitejs/plugin-react-swc": "^4.1.0", "@vitest/coverage-v8": "^3.2.4", - "eslint": "^9.36.0", + "eslint": "^9.37.0", "eslint-plugin-react": "^7.37.5", - "eslint-plugin-react-hooks": "^6.1.0", + "eslint-plugin-react-hooks": "^7.0.0", "eslint-plugin-simple-import-sort": "^12.1.1", "globals": "^16.4.0", "jsdom": "^27.0.0", @@ -126,7 +131,7 @@ "postcss-preset-mantine": "^1.18.0", "postcss-simple-vars": "^7.0.1", "typescript": "^5.9.3", - "typescript-eslint": "^8.45.0", + "typescript-eslint": "^8.46.1", "vite": "^7.1.7", "vitest": "^3.2.4" }, diff --git a/frontend/public/locales/en-GB/translation.json b/frontend/public/locales/en-GB/translation.json index 01a8278f6b..f6f8d828c6 100644 --- a/frontend/public/locales/en-GB/translation.json +++ b/frontend/public/locales/en-GB/translation.json @@ -1,4 +1,21 @@ { + "toolPanel": { + "modePrompt": { + "title": "Choose how you browse tools", + "description": "Preview both layouts and decide how you want to explore Stirling PDF tools.", + "sidebarTitle": "Sidebar mode", + "sidebarDescription": "Keep tools alongside your workspace for quick switching.", + "recommended": "Recommended", + "chooseSidebar": "Use sidebar mode", + "fullscreenTitle": "Fullscreen mode - (legacy)", + "fullscreenDescription": "Browse every tool in a catalogue that covers the workspace until you pick one.", + "chooseFullscreen": "Use fullscreen mode", + "dismiss": "Maybe later" + }, + "fullscreen": { + "showDetails": "Show Details" + } + }, "unsavedChanges": "You have unsaved changes to your PDF.", "areYouSure": "Are you sure you want to leave?", "unsavedChangesTitle": "Unsaved Changes", @@ -73,6 +90,11 @@ "moreOptions": "More Options", "editYourNewFiles": "Edit your new file(s)", "close": "Close", + "openInViewer": "Open in Viewer", + "confirmClose": "Confirm Close", + "confirmCloseMessage": "Are you sure you want to close this file?", + "confirmCloseCancel": "Cancel", + "confirmCloseConfirm": "Close File", "fileSelected": "Selected: {{filename}}", "chooseFile": "Choose File", "filesSelected": "{{count}} files selected", @@ -283,7 +305,8 @@ "capturing": "Press keys… (Esc to cancel)", "change": "Change shortcut", "reset": "Reset", - "shortcut": "Shortcut" + "shortcut": "Shortcut", + "noShortcut": "No shortcut set" } }, "changeCreds": { @@ -597,11 +620,6 @@ "title": "Redact", "desc": "Redacts (blacks out) a PDF based on selected text, drawn shapes and/or selected page(s)" }, - "overlayPdfs": { - "tags": "overlay,combine,stack", - "title": "Overlay PDFs", - "desc": "Overlays PDFs on-top of another PDF" - }, "splitBySections": { "tags": "split,sections,divide", "title": "Split PDF by Sections", @@ -1818,8 +1836,16 @@ "placeholder": "Enter your full name" }, "instructions": { - "title": "How to add signature" + "title": "How to add signature", + "canvas": "After drawing your signature in the canvas, close the modal then click anywhere on the PDF to place it.", + "image": "After uploading your signature image above, click anywhere on the PDF to place it.", + "text": "After entering your name above, click anywhere on the PDF to place your signature." }, + "mode": { + "move": "Move Signature", + "place": "Place Signature" + }, + "updateAndPlace": "Update and Place", "activate": "Activate Signature Placement", "deactivate": "Stop Placing Signatures", "results": { @@ -2541,11 +2567,15 @@ "overlay-pdfs": { "tags": "Overlay", "header": "Overlay PDF Files", + "title": "Overlay PDFs", + "desc": "Overlay one PDF on top of another", "baseFile": { "label": "Select Base PDF File" }, "overlayFiles": { - "label": "Select Overlay PDF Files" + "label": "Select Overlay PDF Files", + "placeholder": "Choose PDF(s)...", + "addMore": "Add more PDFs..." }, "mode": { "label": "Select Overlay Mode", @@ -2555,14 +2585,49 @@ }, "counts": { "label": "Overlay Counts (for Fixed Repeat Mode)", - "placeholder": "Enter comma-separated counts (e.g., 2,3,1)" + "placeholder": "Enter comma-separated counts (e.g., 2,3,1)", + "item": "Count for file" }, "position": { "label": "Select Overlay Position", "foreground": "Foreground", "background": "Background" }, - "submit": "Submit" + "submit": "Submit", + "settings": { + "title": "Settings" + }, + "results": { + "title": "Overlay Results" + }, + "tooltip": { + "header": { + "title": "Overlay PDFs Overview" + }, + "description": { + "title": "Description", + "text": "Combine a base PDF with one or more overlay PDFs. Overlays can be applied page-by-page in different modes and placed in the foreground or background." + }, + "mode": { + "title": "Overlay Mode", + "text": "Choose how to distribute overlay pages across the base PDF pages.", + "sequential": "Sequential Overlay: Use pages from the first overlay PDF until it ends, then move to the next.", + "interleaved": "Interleaved Overlay: Take one page from each overlay in turn.", + "fixedRepeat": "Fixed Repeat Overlay: Take a set number of pages from each overlay before moving to the next. Use Counts to set the numbers." + }, + "position": { + "title": "Overlay Position", + "text": "Foreground places the overlay on top of the page. Background places it behind." + }, + "overlayFiles": { + "title": "Overlay Files", + "text": "Select one or more PDFs to overlay on the base. The order of these files affects how pages are applied in Sequential and Fixed Repeat modes." + }, + "counts": { + "title": "Counts (Fixed Repeat only)", + "text": "Provide a positive number for each overlay file showing how many pages to take before moving to the next. Required when mode is Fixed Repeat." + } + } }, "split-by-sections": { "tags": "Section Split, Divide, Customize,Customise", diff --git a/frontend/public/locales/en-US/translation.json b/frontend/public/locales/en-US/translation.json index b4ac8302dd..c92c4c56e4 100644 --- a/frontend/public/locales/en-US/translation.json +++ b/frontend/public/locales/en-US/translation.json @@ -2,6 +2,23 @@ "language": { "direction": "ltr" }, + "toolPanel": { + "modePrompt": { + "title": "Choose how you browse tools", + "description": "Preview both layouts and decide how you want to explore Stirling PDF tools.", + "sidebarTitle": "Sidebar mode", + "sidebarDescription": "Keep tools alongside your workspace for quick switching.", + "recommended": "Recommended", + "chooseSidebar": "Use sidebar mode", + "fullscreenTitle": "Fullscreen mode - (legacy)", + "fullscreenDescription": "Browse every tool in a catalogue that covers the workspace until you pick one.", + "chooseFullscreen": "Use fullscreen mode", + "dismiss": "Maybe later" + }, + "fullscreen": { + "showDetails": "Show Details" + } + }, "addPageNumbers": { "fontSize": "Font Size", "fontName": "Font Name", @@ -1521,11 +1538,15 @@ "overlay-pdfs": { "tags": "Overlay", "header": "Overlay PDF Files", + "title": "Overlay PDFs", + "desc": "Overlay one PDF on top of another", "baseFile": { "label": "Select Base PDF File" }, "overlayFiles": { - "label": "Select Overlay PDF Files" + "label": "Select Overlay PDF Files", + "placeholder": "Choose PDF(s)...", + "addMore": "Add more PDFs..." }, "mode": { "label": "Select Overlay Mode", @@ -1535,14 +1556,49 @@ }, "counts": { "label": "Overlay Counts (for Fixed Repeat Mode)", - "placeholder": "Enter comma-separated counts (e.g., 2,3,1)" + "placeholder": "Enter comma-separated counts (e.g., 2,3,1)", + "item": "Count for file" }, "position": { "label": "Select Overlay Position", "foreground": "Foreground", "background": "Background" }, - "submit": "Submit" + "submit": "Submit", + "settings": { + "title": "Settings" + }, + "results": { + "title": "Overlay Results" + }, + "tooltip": { + "header": { + "title": "Overlay PDFs Overview" + }, + "description": { + "title": "Description", + "text": "Combine a base PDF with one or more overlay PDFs. Overlays can be applied page-by-page in different modes and placed in the foreground or background." + }, + "mode": { + "title": "Overlay Mode", + "text": "Choose how to distribute overlay pages across the base PDF pages.", + "sequential": "Sequential Overlay: Use pages from the first overlay PDF until it ends, then move to the next.", + "interleaved": "Interleaved Overlay: Take one page from each overlay in turn.", + "fixedRepeat": "Fixed Repeat Overlay: Take a set number of pages from each overlay before moving to the next. Use Counts to set the numbers." + }, + "position": { + "title": "Overlay Position", + "text": "Foreground places the overlay on top of the page. Background places it behind." + }, + "overlayFiles": { + "title": "Overlay Files", + "text": "Select one or more PDFs to overlay on the base. The order of these files affects how pages are applied in Sequential and Fixed Repeat modes." + }, + "counts": { + "title": "Counts (Fixed Repeat only)", + "text": "Provide a positive number for each overlay file showing how many pages to take before moving to the next. Required when mode is Fixed Repeat." + } + } }, "split-by-sections": { "tags": "Section Split, Divide, Customize", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 88c19649fd..5db513d4aa 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -40,9 +40,9 @@ const LoadingFallback = () => ( export default function App() { return ( }> - - - + + + @@ -62,9 +62,9 @@ export default function App() { - - - + + + ); } diff --git a/frontend/src/components/FileManager.tsx b/frontend/src/components/FileManager.tsx index fb26a3a3ae..4f8c203caf 100644 --- a/frontend/src/components/FileManager.tsx +++ b/frontend/src/components/FileManager.tsx @@ -9,6 +9,9 @@ import MobileLayout from './fileManager/MobileLayout'; import DesktopLayout from './fileManager/DesktopLayout'; import DragOverlay from './fileManager/DragOverlay'; import { FileManagerProvider } from '../contexts/FileManagerContext'; +import { Z_INDEX_FILE_MANAGER_MODAL } from '../styles/zIndex'; +import { isGoogleDriveConfigured } from '../services/googleDrivePickerService'; +import { loadScript } from '../utils/scriptLoader'; interface FileManagerProps { selectedTool?: Tool | null; @@ -84,6 +87,30 @@ const FileManager: React.FC = ({ selectedTool }) => { }; }, []); + // Preload Google Drive scripts if configured + + useEffect(() => { + if (isGoogleDriveConfigured()) { + // Load scripts in parallel without blocking + Promise.all([ + loadScript({ + src: 'https://apis.google.com/js/api.js', + id: 'gapi-script', + async: true, + defer: true, + }), + loadScript({ + src: 'https://accounts.google.com/gsi/client', + id: 'gis-script', + async: true, + defer: true, + }), + ]).catch((error) => { + console.warn('Failed to preload Google Drive scripts:', error); + }); + } + }, []); + // Modal size constants for consistent scaling const modalHeight = '80vh'; const modalWidth = isMobile ? '100%' : '80vw'; @@ -100,6 +127,7 @@ const FileManager: React.FC = ({ selectedTool }) => { radius="md" className="overflow-hidden p-0" withCloseButton={false} + zIndex={Z_INDEX_FILE_MANAGER_MODAL} styles={{ content: { position: 'relative', diff --git a/frontend/src/components/annotation/shared/DrawingCanvas.tsx b/frontend/src/components/annotation/shared/DrawingCanvas.tsx index d4ae74ad07..87362f74dd 100644 --- a/frontend/src/components/annotation/shared/DrawingCanvas.tsx +++ b/frontend/src/components/annotation/shared/DrawingCanvas.tsx @@ -1,7 +1,8 @@ -import React, { useRef, useState, useCallback } from 'react'; -import { Paper, Group, Button, Modal, Stack, Text } from '@mantine/core'; +import React, { useRef, useState } from 'react'; +import { Paper, Button, Modal, Stack, Text, Popover, ColorPicker as MantineColorPicker } from '@mantine/core'; import { ColorSwatchButton } from './ColorPicker'; import PenSizeSelector from '../../tools/sign/PenSizeSelector'; +import SignaturePad from 'signature_pad'; interface DrawingCanvasProps { selectedColor: string; @@ -11,6 +12,7 @@ interface DrawingCanvasProps { onPenSizeChange: (size: number) => void; onPenSizeInputChange: (input: string) => void; onSignatureDataChange: (data: string | null) => void; + onDrawingComplete?: () => void; disabled?: boolean; width?: number; height?: number; @@ -27,411 +29,253 @@ export const DrawingCanvas: React.FC = ({ onPenSizeChange, onPenSizeInputChange, onSignatureDataChange, + onDrawingComplete, disabled = false, width = 400, height = 150, - modalWidth = 800, - modalHeight = 400, - additionalButtons }) => { - const canvasRef = useRef(null); + const previewCanvasRef = useRef(null); const modalCanvasRef = useRef(null); - const visibleModalCanvasRef = useRef(null); + const padRef = useRef(null); + const [modalOpen, setModalOpen] = useState(false); + const [colorPickerOpen, setColorPickerOpen] = useState(false); - const [isDrawing, setIsDrawing] = useState(false); - const [isModalDrawing, setIsModalDrawing] = useState(false); - const [isModalOpen, setIsModalOpen] = useState(false); + const initPad = (canvas: HTMLCanvasElement) => { + if (!padRef.current) { + const rect = canvas.getBoundingClientRect(); + canvas.width = rect.width; + canvas.height = rect.height; - // Drawing functions for main canvas - const startDrawing = useCallback((e: React.MouseEvent) => { - if (!canvasRef.current || disabled) return; - - setIsDrawing(true); - const rect = canvasRef.current.getBoundingClientRect(); - const scaleX = canvasRef.current.width / rect.width; - const scaleY = canvasRef.current.height / rect.height; - const x = (e.clientX - rect.left) * scaleX; - const y = (e.clientY - rect.top) * scaleY; - - const ctx = canvasRef.current.getContext('2d'); - if (ctx) { - ctx.strokeStyle = selectedColor; - ctx.lineWidth = penSize; - ctx.lineCap = 'round'; - ctx.lineJoin = 'round'; - ctx.beginPath(); - ctx.moveTo(x, y); + padRef.current = new SignaturePad(canvas, { + penColor: selectedColor, + minWidth: penSize * 0.5, + maxWidth: penSize * 2.5, + throttle: 10, + minDistance: 5, + velocityFilterWeight: 0.7, + }); } - }, [disabled, selectedColor, penSize]); + }; - const draw = useCallback((e: React.MouseEvent) => { - if (!isDrawing || !canvasRef.current || disabled) return; - - const rect = canvasRef.current.getBoundingClientRect(); - const scaleX = canvasRef.current.width / rect.width; - const scaleY = canvasRef.current.height / rect.height; - const x = (e.clientX - rect.left) * scaleX; - const y = (e.clientY - rect.top) * scaleY; - - const ctx = canvasRef.current.getContext('2d'); - if (ctx) { - ctx.lineTo(x, y); - ctx.stroke(); + const openModal = () => { + // Clear pad ref so it reinitializes + if (padRef.current) { + padRef.current.off(); + padRef.current = null; } - }, [isDrawing, disabled]); + setModalOpen(true); + }; - const stopDrawing = useCallback(() => { - if (!isDrawing || disabled) return; + const trimCanvas = (canvas: HTMLCanvasElement): string => { + const ctx = canvas.getContext('2d'); + if (!ctx) return canvas.toDataURL('image/png'); - setIsDrawing(false); + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + const pixels = imageData.data; - // Save canvas as signature data - if (canvasRef.current) { - const dataURL = canvasRef.current.toDataURL('image/png'); - onSignatureDataChange(dataURL); - } - }, [isDrawing, disabled, onSignatureDataChange]); + let minX = canvas.width, minY = canvas.height, maxX = 0, maxY = 0; - // Modal canvas drawing functions - const startModalDrawing = useCallback((e: React.MouseEvent) => { - if (!visibleModalCanvasRef.current || !modalCanvasRef.current) return; - - setIsModalDrawing(true); - const rect = visibleModalCanvasRef.current.getBoundingClientRect(); - const scaleX = visibleModalCanvasRef.current.width / rect.width; - const scaleY = visibleModalCanvasRef.current.height / rect.height; - const x = (e.clientX - rect.left) * scaleX; - const y = (e.clientY - rect.top) * scaleY; - - // Draw on both the visible modal canvas and hidden canvas - const visibleCtx = visibleModalCanvasRef.current.getContext('2d'); - const hiddenCtx = modalCanvasRef.current.getContext('2d'); - - [visibleCtx, hiddenCtx].forEach(ctx => { - if (ctx) { - ctx.strokeStyle = selectedColor; - ctx.lineWidth = penSize; - ctx.lineCap = 'round'; - ctx.lineJoin = 'round'; - ctx.beginPath(); - ctx.moveTo(x, y); - } - }); - }, [selectedColor, penSize]); - - const drawModal = useCallback((e: React.MouseEvent) => { - if (!isModalDrawing || !visibleModalCanvasRef.current || !modalCanvasRef.current) return; - - const rect = visibleModalCanvasRef.current.getBoundingClientRect(); - const scaleX = visibleModalCanvasRef.current.width / rect.width; - const scaleY = visibleModalCanvasRef.current.height / rect.height; - const x = (e.clientX - rect.left) * scaleX; - const y = (e.clientY - rect.top) * scaleY; - - // Draw on both canvases - const visibleCtx = visibleModalCanvasRef.current.getContext('2d'); - const hiddenCtx = modalCanvasRef.current.getContext('2d'); - - [visibleCtx, hiddenCtx].forEach(ctx => { - if (ctx) { - ctx.lineTo(x, y); - ctx.stroke(); - } - }); - }, [isModalDrawing]); - - const stopModalDrawing = useCallback(() => { - if (!isModalDrawing) return; - setIsModalDrawing(false); - - // Sync the canvases and update signature data (only when drawing stops) - if (modalCanvasRef.current) { - const dataURL = modalCanvasRef.current.toDataURL('image/png'); - onSignatureDataChange(dataURL); - - // Also update the small canvas display - if (canvasRef.current) { - const smallCtx = canvasRef.current.getContext('2d'); - if (smallCtx) { - const img = new Image(); - img.onload = () => { - smallCtx.clearRect(0, 0, canvasRef.current!.width, canvasRef.current!.height); - smallCtx.drawImage(img, 0, 0, canvasRef.current!.width, canvasRef.current!.height); - }; - img.src = dataURL; + // Find bounds of non-transparent pixels + for (let y = 0; y < canvas.height; y++) { + for (let x = 0; x < canvas.width; x++) { + const alpha = pixels[(y * canvas.width + x) * 4 + 3]; + if (alpha > 0) { + if (x < minX) minX = x; + if (x > maxX) maxX = x; + if (y < minY) minY = y; + if (y > maxY) maxY = y; } } } - }, [isModalDrawing]); - // Clear canvas functions - const clearCanvas = useCallback(() => { - if (!canvasRef.current || disabled) return; + const trimWidth = maxX - minX + 1; + const trimHeight = maxY - minY + 1; - const ctx = canvasRef.current.getContext('2d'); - if (ctx) { - ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height); - - // Also clear the modal canvas if it exists - if (modalCanvasRef.current) { - const modalCtx = modalCanvasRef.current.getContext('2d'); - if (modalCtx) { - modalCtx.clearRect(0, 0, modalCanvasRef.current.width, modalCanvasRef.current.height); - } - } - - onSignatureDataChange(null); - } - }, [disabled]); - - const clearModalCanvas = useCallback(() => { - // Clear both modal canvases (visible and hidden) - if (modalCanvasRef.current) { - const hiddenCtx = modalCanvasRef.current.getContext('2d'); - if (hiddenCtx) { - hiddenCtx.clearRect(0, 0, modalCanvasRef.current.width, modalCanvasRef.current.height); - } + // Create trimmed canvas + const trimmedCanvas = document.createElement('canvas'); + trimmedCanvas.width = trimWidth; + trimmedCanvas.height = trimHeight; + const trimmedCtx = trimmedCanvas.getContext('2d'); + if (trimmedCtx) { + trimmedCtx.drawImage(canvas, minX, minY, trimWidth, trimHeight, 0, 0, trimWidth, trimHeight); } - if (visibleModalCanvasRef.current) { - const visibleCtx = visibleModalCanvasRef.current.getContext('2d'); - if (visibleCtx) { - visibleCtx.clearRect(0, 0, visibleModalCanvasRef.current.width, visibleModalCanvasRef.current.height); - } - } + return trimmedCanvas.toDataURL('image/png'); + }; - // Also clear the main canvas and signature data - if (canvasRef.current) { - const mainCtx = canvasRef.current.getContext('2d'); - if (mainCtx) { - mainCtx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height); - } - } + const closeModal = () => { + if (padRef.current && !padRef.current.isEmpty()) { + const canvas = modalCanvasRef.current; + if (canvas) { + const trimmedPng = trimCanvas(canvas); + onSignatureDataChange(trimmedPng); - onSignatureDataChange(null); - }, []); - - const saveModalSignature = useCallback(() => { - if (!modalCanvasRef.current) return; - - const dataURL = modalCanvasRef.current.toDataURL('image/png'); - onSignatureDataChange(dataURL); - - // Copy to small canvas for display - if (canvasRef.current) { - const ctx = canvasRef.current.getContext('2d'); - if (ctx) { + // Update preview canvas with proper aspect ratio const img = new Image(); img.onload = () => { - ctx.clearRect(0, 0, canvasRef.current!.width, canvasRef.current!.height); - ctx.drawImage(img, 0, 0, canvasRef.current!.width, canvasRef.current!.height); + if (previewCanvasRef.current) { + const ctx = previewCanvasRef.current.getContext('2d'); + if (ctx) { + ctx.clearRect(0, 0, previewCanvasRef.current.width, previewCanvasRef.current.height); + + // Calculate scaling to fit within preview canvas while maintaining aspect ratio + const scale = Math.min( + previewCanvasRef.current.width / img.width, + previewCanvasRef.current.height / img.height + ); + const scaledWidth = img.width * scale; + const scaledHeight = img.height * scale; + const x = (previewCanvasRef.current.width - scaledWidth) / 2; + const y = (previewCanvasRef.current.height - scaledHeight) / 2; + + ctx.drawImage(img, x, y, scaledWidth, scaledHeight); + } + } }; - img.src = dataURL; - } - } + img.src = trimmedPng; - setIsModalOpen(false); - }, []); - - const openModal = useCallback(() => { - setIsModalOpen(true); - // Copy content to modal canvas after a brief delay - setTimeout(() => { - if (visibleModalCanvasRef.current && modalCanvasRef.current) { - const visibleCtx = visibleModalCanvasRef.current.getContext('2d'); - if (visibleCtx) { - visibleCtx.strokeStyle = selectedColor; - visibleCtx.lineWidth = penSize; - visibleCtx.lineCap = 'round'; - visibleCtx.lineJoin = 'round'; - visibleCtx.clearRect(0, 0, visibleModalCanvasRef.current.width, visibleModalCanvasRef.current.height); - visibleCtx.drawImage(modalCanvasRef.current, 0, 0, visibleModalCanvasRef.current.width, visibleModalCanvasRef.current.height); + if (onDrawingComplete) { + onDrawingComplete(); } } - }, 300); - }, [selectedColor, penSize]); + } + if (padRef.current) { + padRef.current.off(); + padRef.current = null; + } + setModalOpen(false); + }; - // Initialize canvas settings whenever color or pen size changes - React.useEffect(() => { - const updateCanvas = (canvas: HTMLCanvasElement | null) => { - if (!canvas) return; - const ctx = canvas.getContext('2d'); + const clear = () => { + if (padRef.current) { + padRef.current.clear(); + } + if (previewCanvasRef.current) { + const ctx = previewCanvasRef.current.getContext('2d'); if (ctx) { - ctx.strokeStyle = selectedColor; - ctx.lineWidth = penSize; - ctx.lineCap = 'round'; - ctx.lineJoin = 'round'; + ctx.clearRect(0, 0, previewCanvasRef.current.width, previewCanvasRef.current.height); } - }; + } + onSignatureDataChange(null); + }; - updateCanvas(canvasRef.current); - updateCanvas(modalCanvasRef.current); - updateCanvas(visibleModalCanvasRef.current); - }, [selectedColor, penSize]); + const updatePenColor = (color: string) => { + if (padRef.current) { + padRef.current.penColor = color; + } + }; + + const updatePenSize = (size: number) => { + if (padRef.current) { + padRef.current.minWidth = size * 0.8; + padRef.current.maxWidth = size * 1.2; + } + }; return ( <> - - Draw your signature - -
- Color - - - -
-
- Pen Size - -
-
- -
-
-
+ Draw your signature - -
- {additionalButtons} -
- -
+ + Click to open drawing canvas +
- {/* Hidden canvas for modal synchronization */} - - - {/* Modal for larger signature canvas */} - setIsModalOpen(false)} - title="Draw Your Signature" - size="xl" - centered - > + - {/* Color and Pen Size picker */} - - -
- Color - -
-
- Pen Size - -
-
-
+
+
+ Color + + +
+ setColorPickerOpen(!colorPickerOpen)} + /> +
+
+ + { + onColorSwatchClick(); + updatePenColor(color); + }} + swatches={['#000000', '#0066cc', '#cc0000', '#cc6600', '#009900', '#6600cc']} + /> + +
+
+
+ Pen Size + { + onPenSizeChange(size); + updatePenSize(size); + }} + onInputChange={onPenSizeInputChange} + placeholder="Size" + size="compact-sm" + style={{ width: '60px' }} + /> +
+
- - - + { + modalCanvasRef.current = el; + if (el) initPad(el); + }} + style={{ + border: '1px solid #ccc', + borderRadius: '4px', + display: 'block', + touchAction: 'none', + backgroundColor: 'white', + width: '100%', + maxWidth: '800px', + height: '400px', + cursor: 'crosshair', + }} + /> - - - - - - - + +
); }; -export default DrawingCanvas; \ No newline at end of file +export default DrawingCanvas; diff --git a/frontend/src/components/annotation/shared/ImageUploader.tsx b/frontend/src/components/annotation/shared/ImageUploader.tsx index d590a7bc19..aabca815cc 100644 --- a/frontend/src/components/annotation/shared/ImageUploader.tsx +++ b/frontend/src/components/annotation/shared/ImageUploader.tsx @@ -48,7 +48,7 @@ export const ImageUploader: React.FC = ({ disabled={disabled} /> - {hint || t('sign.image.hint', 'Upload a PNG or JPG image of your signature')} + {hint || t('sign.image.hint', 'Upload an image of your signature')} ); diff --git a/frontend/src/components/annotation/shared/TextInputWithFont.tsx b/frontend/src/components/annotation/shared/TextInputWithFont.tsx index b85511cd54..b7af602953 100644 --- a/frontend/src/components/annotation/shared/TextInputWithFont.tsx +++ b/frontend/src/components/annotation/shared/TextInputWithFont.tsx @@ -1,6 +1,7 @@ import React, { useState, useEffect } from 'react'; -import { Stack, TextInput, Select, Combobox, useCombobox } from '@mantine/core'; +import { Stack, TextInput, Select, Combobox, useCombobox, Group, Box } from '@mantine/core'; import { useTranslation } from 'react-i18next'; +import { ColorPicker } from './ColorPicker'; interface TextInputWithFontProps { text: string; @@ -9,6 +10,8 @@ interface TextInputWithFontProps { onFontSizeChange: (size: number) => void; fontFamily: string; onFontFamilyChange: (family: string) => void; + textColor?: string; + onTextColorChange?: (color: string) => void; disabled?: boolean; label?: string; placeholder?: string; @@ -21,6 +24,8 @@ export const TextInputWithFont: React.FC = ({ onFontSizeChange, fontFamily, onFontFamilyChange, + textColor = '#000000', + onTextColorChange, disabled = false, label, placeholder @@ -28,6 +33,7 @@ export const TextInputWithFont: React.FC = ({ const { t } = useTranslation(); const [fontSizeInput, setFontSizeInput] = useState(fontSize.toString()); const fontSizeCombobox = useCombobox(); + const [isColorPickerOpen, setIsColorPickerOpen] = useState(false); // Sync font size input with prop changes useEffect(() => { @@ -42,7 +48,7 @@ export const TextInputWithFont: React.FC = ({ { value: 'Georgia', label: 'Georgia' }, ]; - const fontSizeOptions = ['8', '12', '16', '20', '24', '28', '32', '36', '40', '48']; + const fontSizeOptions = ['8', '12', '16', '20', '24', '28', '32', '36', '40', '48', '56', '64', '72', '80', '96', '112', '128', '144', '160', '176', '192', '200']; return ( @@ -66,61 +72,101 @@ export const TextInputWithFont: React.FC = ({ allowDeselect={false} /> - {/* Font Size */} - { - setFontSizeInput(optionValue); - const size = parseInt(optionValue); - if (!isNaN(size)) { - onFontSizeChange(size); - } - fontSizeCombobox.closeDropdown(); - }} - store={fontSizeCombobox} - withinPortal={false} - > - - { - const value = event.currentTarget.value; - setFontSizeInput(value); + {/* Font Size and Color */} + + { + setFontSizeInput(optionValue); + const size = parseInt(optionValue); + if (!isNaN(size)) { + onFontSizeChange(size); + } + fontSizeCombobox.closeDropdown(); + }} + store={fontSizeCombobox} + withinPortal={false} + > + + { + const value = event.currentTarget.value; + setFontSizeInput(value); - // Parse and validate the typed value in real-time - const size = parseInt(value); - if (!isNaN(size) && size >= 8 && size <= 72) { - onFontSizeChange(size); + // Parse and validate the typed value in real-time + const size = parseInt(value); + if (!isNaN(size) && size >= 8 && size <= 200) { + onFontSizeChange(size); + } + + fontSizeCombobox.openDropdown(); + fontSizeCombobox.updateSelectedOptionIndex(); + }} + onClick={() => fontSizeCombobox.openDropdown()} + onFocus={() => fontSizeCombobox.openDropdown()} + onBlur={() => { + fontSizeCombobox.closeDropdown(); + // Clean up invalid values on blur + const size = parseInt(fontSizeInput); + if (isNaN(size) || size < 8 || size > 200) { + setFontSizeInput(fontSize.toString()); + } else { + onFontSizeChange(size); + } + }} + disabled={disabled} + /> + + + + + {fontSizeOptions.map((size) => ( + + {size}px + + ))} + + + + + {/* Text Color Picker */} + {onTextColorChange && ( + + !disabled && setIsColorPickerOpen(true)} + style={{ cursor: disabled ? 'default' : 'pointer' }} + rightSection={ + } + /> + + )} + - fontSizeCombobox.openDropdown(); - fontSizeCombobox.updateSelectedOptionIndex(); - }} - onClick={() => fontSizeCombobox.openDropdown()} - onFocus={() => fontSizeCombobox.openDropdown()} - onBlur={() => { - fontSizeCombobox.closeDropdown(); - // Clean up invalid values on blur - const size = parseInt(fontSizeInput); - if (isNaN(size) || size < 8 || size > 72) { - setFontSizeInput(fontSize.toString()); - } - }} - disabled={disabled} - /> - - - - - {fontSizeOptions.map((size) => ( - - {size}px - - ))} - - - + {/* Color Picker Modal */} + {onTextColorChange && ( + setIsColorPickerOpen(false)} + selectedColor={textColor} + onColorChange={onTextColorChange} + /> + )} ); }; \ No newline at end of file diff --git a/frontend/src/components/fileEditor/FileEditor.module.css b/frontend/src/components/fileEditor/FileEditor.module.css index 17184bbf4f..4f26c8bce5 100644 --- a/frontend/src/components/fileEditor/FileEditor.module.css +++ b/frontend/src/components/fileEditor/FileEditor.module.css @@ -9,7 +9,7 @@ transition: box-shadow 0.18s ease, outline-color 0.18s ease, transform 0.18s ease; max-width: 100%; max-height: 100%; - overflow: hidden; + overflow: visible; margin-left: 0.5rem; margin-right: 0.5rem; } diff --git a/frontend/src/components/fileEditor/FileEditor.tsx b/frontend/src/components/fileEditor/FileEditor.tsx index 90ee6eebe6..856c55b836 100644 --- a/frontend/src/components/fileEditor/FileEditor.tsx +++ b/frontend/src/components/fileEditor/FileEditor.tsx @@ -1,6 +1,6 @@ -import React, { useState, useCallback, useRef, useMemo } from 'react'; +import React, { useState, useCallback, useRef, useMemo, useEffect } from 'react'; import { - Text, Center, Box, LoadingOverlay, Stack, Group + Text, Center, Box, LoadingOverlay, Stack } from '@mantine/core'; import { Dropzone } from '@mantine/dropzone'; import { useFileSelection, useFileState, useFileManagement, useFileActions } from '../../contexts/FileContext'; @@ -10,7 +10,6 @@ import { detectFileExtension } from '../../utils/fileUtils'; import FileEditorThumbnail from './FileEditorThumbnail'; import AddFileCard from './AddFileCard'; import FilePickerModal from '../shared/FilePickerModal'; -import SkeletonLoader from '../shared/SkeletonLoader'; import { FileId, StirlingFile } from '../../types/fileContext'; import { alert } from '../toast'; import { downloadBlob } from '../../utils/downloadUtils'; @@ -62,25 +61,12 @@ const FileEditor = ({ const [selectionMode, setSelectionMode] = useState(toolMode); // Enable selection mode automatically in tool mode - React.useEffect(() => { + useEffect(() => { if (toolMode) { setSelectionMode(true); } }, [toolMode]); const [showFilePickerModal, setShowFilePickerModal] = useState(false); - const [zipExtractionProgress, setZipExtractionProgress] = useState<{ - isExtracting: boolean; - currentFile: string; - progress: number; - extractedCount: number; - totalFiles: number; - }>({ - isExtracting: false, - currentFile: '', - progress: 0, - extractedCount: 0, - totalFiles: 0 - }); // Get selected file IDs from context (defensive programming) const contextSelectedIds = Array.isArray(selectedFileIds) ? selectedFileIds : []; @@ -92,106 +78,26 @@ const FileEditor = ({ const localSelectedIds = contextSelectedIds; // Process uploaded files using context + // ZIP extraction is now handled automatically in FileContext based on user preferences const handleFileUpload = useCallback(async (uploadedFiles: File[]) => { _setError(null); try { - const allExtractedFiles: File[] = []; - const errors: string[] = []; - - for (const file of uploadedFiles) { - if (file.type === 'application/pdf') { - // Handle PDF files normally - allExtractedFiles.push(file); - } else if (file.type === 'application/zip' || file.type === 'application/x-zip-compressed' || file.name.toLowerCase().endsWith('.zip')) { - // Handle ZIP files - only expand if they contain PDFs - try { - // Validate ZIP file first - const validation = await zipFileService.validateZipFile(file); - - if (validation.isValid && validation.containsPDFs) { - // ZIP contains PDFs - extract them - setZipExtractionProgress({ - isExtracting: true, - currentFile: file.name, - progress: 0, - extractedCount: 0, - totalFiles: validation.fileCount - }); - - const extractionResult = await zipFileService.extractPdfFiles(file, (progress) => { - setZipExtractionProgress({ - isExtracting: true, - currentFile: progress.currentFile, - progress: progress.progress, - extractedCount: progress.extractedCount, - totalFiles: progress.totalFiles - }); - }); - - // Reset extraction progress - setZipExtractionProgress({ - isExtracting: false, - currentFile: '', - progress: 0, - extractedCount: 0, - totalFiles: 0 - }); - - if (extractionResult.success) { - allExtractedFiles.push(...extractionResult.extractedFiles); - - if (extractionResult.errors.length > 0) { - errors.push(...extractionResult.errors); - } - } else { - errors.push(`Failed to extract ZIP file "${file.name}": ${extractionResult.errors.join(', ')}`); - } - } else { - // ZIP doesn't contain PDFs or is invalid - treat as regular file - allExtractedFiles.push(file); - } - } catch (zipError) { - errors.push(`Failed to process ZIP file "${file.name}": ${zipError instanceof Error ? zipError.message : 'Unknown error'}`); - setZipExtractionProgress({ - isExtracting: false, - currentFile: '', - progress: 0, - extractedCount: 0, - totalFiles: 0 - }); - } - } else { - allExtractedFiles.push(file); - } - } - - // Show any errors - if (errors.length > 0) { - showError(errors.join('\n')); - } - - // Process all extracted files - if (allExtractedFiles.length > 0) { - // Add files to context and select them automatically - await addFiles(allExtractedFiles, { selectFiles: true }); - showStatus(`Added ${allExtractedFiles.length} files`, 'success'); + if (uploadedFiles.length > 0) { + // FileContext will automatically handle ZIP extraction based on user preferences + // - Respects autoUnzip setting + // - Respects autoUnzipFileLimit + // - HTML ZIPs stay intact + // - Non-ZIP files pass through unchanged + await addFiles(uploadedFiles, { selectFiles: true }); + showStatus(`Added ${uploadedFiles.length} file(s)`, 'success'); } } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Failed to process files'; showError(errorMessage); console.error('File processing error:', err); - - // Reset extraction progress on error - setZipExtractionProgress({ - isExtracting: false, - currentFile: '', - progress: 0, - extractedCount: 0, - totalFiles: 0 - }); } - }, [addFiles]); + }, [addFiles, showStatus, showError]); const toggleFile = useCallback((fileId: FileId) => { const currentSelectedIds = contextSelectedIdsRef.current; @@ -394,7 +300,7 @@ const FileEditor = ({ - {activeStirlingFileStubs.length === 0 && !zipExtractionProgress.isExtracting ? ( + {activeStirlingFileStubs.length === 0 ? (
📁 @@ -402,43 +308,6 @@ const FileEditor = ({ Upload PDF files, ZIP archives, or load from storage to get started
- ) : activeStirlingFileStubs.length === 0 && zipExtractionProgress.isExtracting ? ( - - - - {/* ZIP Extraction Progress */} - {zipExtractionProgress.isExtracting && ( - - - Extracting ZIP archive... - {Math.round(zipExtractionProgress.progress)}% - - - {zipExtractionProgress.currentFile || 'Processing files...'} - - - {zipExtractionProgress.extractedCount} of {zipExtractionProgress.totalFiles} files extracted - -
-
-
- - )} - - - - ) : (
(null); - const [actionsWidth, setActionsWidth] = useState(undefined); - const [showActions, setShowActions] = useState(false); + const [showHoverMenu, setShowHoverMenu] = useState(false); + const isMobile = useMediaQuery('(max-width: 1024px)'); + const [showCloseModal, setShowCloseModal] = useState(false); // Resolve the actual File object for pin/unpin operations const actualFile = useMemo(() => { @@ -154,46 +158,66 @@ const FileEditorThumbnail = ({ }; }, [file.id, file.name, selectedFiles, onReorderFiles]); - // Update dropdown width on resize - useEffect(() => { - const update = () => { - if (dragElementRef.current) setActionsWidth(dragElementRef.current.offsetWidth); - }; - update(); - window.addEventListener('resize', update); - return () => window.removeEventListener('resize', update); + // Handle close with confirmation + const handleCloseWithConfirmation = useCallback(() => { + setShowCloseModal(true); }, []); - // Close the actions dropdown when hovering outside this file card (and its dropdown) - useEffect(() => { - if (!showActions) return; + const handleConfirmClose = useCallback(() => { + onCloseFile(file.id); + alert({ alertType: 'neutral', title: `Closed ${file.name}`, expandable: false, durationMs: 3500 }); + setShowCloseModal(false); + }, [file.id, file.name, onCloseFile]); - const isInsideCard = (target: EventTarget | null) => { - const container = dragElementRef.current; - if (!container) return false; - return target instanceof Node && container.contains(target); - }; + const handleCancelClose = useCallback(() => { + setShowCloseModal(false); + }, []); - const handleMouseMove = (e: MouseEvent) => { - if (!isInsideCard(e.target)) { - setShowActions(false); - } - }; - - const handleTouchStart = (e: TouchEvent) => { - // On touch devices, close if the touch target is outside the card - if (!isInsideCard(e.target)) { - setShowActions(false); - } - }; - - document.addEventListener('mousemove', handleMouseMove); - document.addEventListener('touchstart', handleTouchStart, { passive: true }); - return () => { - document.removeEventListener('mousemove', handleMouseMove); - document.removeEventListener('touchstart', handleTouchStart); - }; - }, [showActions]); + // Build hover menu actions + const hoverActions = useMemo(() => [ + { + id: 'view', + icon: , + label: t('openInViewer', 'Open in Viewer'), + onClick: (e) => { + e.stopPropagation(); + onViewFile(file.id); + }, + }, + { + id: 'download', + icon: , + label: t('download', 'Download'), + onClick: (e) => { + e.stopPropagation(); + onDownloadFile(file.id); + alert({ alertType: 'success', title: `Downloading ${file.name}`, expandable: false, durationMs: 2500 }); + }, + }, + { + id: 'unzip', + icon: , + label: t('fileManager.unzip', 'Unzip'), + onClick: (e) => { + e.stopPropagation(); + if (onUnzipFile) { + onUnzipFile(file.id); + alert({ alertType: 'success', title: `Unzipping ${file.name}`, expandable: false, durationMs: 2500 }); + } + }, + hidden: !isZipFile || !onUnzipFile, + }, + { + id: 'close', + icon: , + label: t('close', 'Close'), + onClick: (e) => { + e.stopPropagation(); + handleCloseWithConfirmation(); + }, + color: 'red', + } + ], [t, file.id, file.name, isZipFile, onViewFile, onDownloadFile, onUnzipFile, handleCloseWithConfirmation]); // ---- Card interactions ---- const handleCardClick = () => { @@ -205,6 +229,11 @@ const FileEditorThumbnail = ({ onToggleFile(file.id); }; + const handleCardDoubleClick = () => { + if (!isSupported) return; + onViewFile(file.id); + }; + // ---- Style helpers ---- const getHeaderClassName = () => { if (hasError) return styles.headerError; @@ -226,6 +255,9 @@ const FileEditorThumbnail = ({ role="listitem" aria-selected={isSelected} onClick={handleCardClick} + onMouseEnter={() => setShowHoverMenu(true)} + onMouseLeave={() => setShowHoverMenu(false)} + onDoubleClick={handleCardDoubleClick} > {/* Header bar */}
: } - - {/* Download icon */} - - { - e.stopPropagation(); - onDownloadFile(file.id); - alert({ alertType: 'success', title: `Downloading ${file.name}`, expandable: false, durationMs: 2500 }); - }} - > - - - - - {/* Kebab menu */} - { - e.stopPropagation(); - setShowActions((v) => !v); - }} - > - -
- {/* Actions overlay */} - {showActions && ( -
e.stopPropagation()} - > - - - - - {isZipFile && onUnzipFile && ( - - )} - -
- - -
- )} - {/* Title + meta line */}
)}
+ + {/* Hover Menu */} + + + {/* Close Confirmation Modal */} + + + {t('confirmCloseMessage', 'Are you sure you want to close this file?')} + + {file.name} + + + + + + +
); }; diff --git a/frontend/src/components/fileManager/CompactFileDetails.tsx b/frontend/src/components/fileManager/CompactFileDetails.tsx index 3656acee62..c7e8334044 100644 --- a/frontend/src/components/fileManager/CompactFileDetails.tsx +++ b/frontend/src/components/fileManager/CompactFileDetails.tsx @@ -42,6 +42,7 @@ const CompactFileDetails: React.FC = ({ {currentFile && thumbnail ? ( {currentFile.name} = ({ {/* File info */} - + {currentFile ? currentFile.name : 'No file selected'} diff --git a/frontend/src/components/fileManager/FileDetails.tsx b/frontend/src/components/fileManager/FileDetails.tsx index b1e7e93e32..d4ef7bd07a 100644 --- a/frontend/src/components/fileManager/FileDetails.tsx +++ b/frontend/src/components/fileManager/FileDetails.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { Stack, Button, Box } from '@mantine/core'; import { useTranslation } from 'react-i18next'; import { useIndexedDBThumbnail } from '../../hooks/useIndexedDBThumbnail'; @@ -50,7 +50,7 @@ const FileDetails: React.FC = ({ }; // Reset index when selection changes - React.useEffect(() => { + useEffect(() => { if (currentFileIndex >= selectedFiles.length) { setCurrentFileIndex(0); } diff --git a/frontend/src/components/fileManager/FileHistoryGroup.tsx b/frontend/src/components/fileManager/FileHistoryGroup.tsx index 16bc02aa13..771499584a 100644 --- a/frontend/src/components/fileManager/FileHistoryGroup.tsx +++ b/frontend/src/components/fileManager/FileHistoryGroup.tsx @@ -44,7 +44,7 @@ const FileHistoryGroup: React.FC = ({ - {sortedHistory.map((historyFile, _index) => ( + {sortedHistory.map((historyFile) => ( = ({ onDoubleClick={() => onFileDoubleClick(historyFile)} isHistoryFile={true} // This enables "Add to Recents" in menu isLatestVersion={false} // History files are never latest - // onAddToRecents is accessed from context by FileListItem /> ))} diff --git a/frontend/src/components/fileManager/FileListItem.tsx b/frontend/src/components/fileManager/FileListItem.tsx index a3353df194..a721961ec7 100644 --- a/frontend/src/components/fileManager/FileListItem.tsx +++ b/frontend/src/components/fileManager/FileListItem.tsx @@ -12,6 +12,7 @@ import { FileId, StirlingFileStub } from '../../types/fileContext'; import { useFileManagerContext } from '../../contexts/FileManagerContext'; import { zipFileService } from '../../services/zipFileService'; import ToolChain from '../shared/ToolChain'; +import { Z_INDEX_OVER_FILE_MANAGER_MODAL } from '../../styles/zIndex'; interface FileListItemProps { file: StirlingFileStub; @@ -40,7 +41,7 @@ const FileListItem: React.FC = ({ const [isHovered, setIsHovered] = useState(false); const [isMenuOpen, setIsMenuOpen] = useState(false); const { t } = useTranslation(); - const {expandedFileIds, onToggleExpansion, onAddToRecents, onUnzipFile } = useFileManagerContext(); + const {expandedFileIds, onToggleExpansion, onUnzipFile } = useFileManagerContext(); // Check if this is a ZIP file const isZipFile = zipFileService.isZipFileStub(file); @@ -127,6 +128,7 @@ const FileListItem: React.FC = ({ withinPortal onOpen={() => setIsMenuOpen(true)} onClose={() => setIsMenuOpen(false)} + zIndex={Z_INDEX_OVER_FILE_MANAGER_MODAL} > = ({ leftSection={} onClick={(e) => { e.stopPropagation(); - onAddToRecents(file); }} > {t('fileManager.restore', 'Restore')} diff --git a/frontend/src/components/fileManager/FileSourceButtons.tsx b/frontend/src/components/fileManager/FileSourceButtons.tsx index d2d28e09e7..78ab8ce39e 100644 --- a/frontend/src/components/fileManager/FileSourceButtons.tsx +++ b/frontend/src/components/fileManager/FileSourceButtons.tsx @@ -5,6 +5,7 @@ import UploadIcon from '@mui/icons-material/Upload'; import CloudIcon from '@mui/icons-material/Cloud'; import { useTranslation } from 'react-i18next'; import { useFileManagerContext } from '../../contexts/FileManagerContext'; +import { useGoogleDrivePicker } from '../../hooks/useGoogleDrivePicker'; interface FileSourceButtonsProps { horizontal?: boolean; @@ -13,8 +14,20 @@ interface FileSourceButtonsProps { const FileSourceButtons: React.FC = ({ horizontal = false }) => { - const { activeSource, onSourceChange, onLocalFileClick } = useFileManagerContext(); + const { activeSource, onSourceChange, onLocalFileClick, onGoogleDriveSelect } = useFileManagerContext(); const { t } = useTranslation(); + const { isEnabled: isGoogleDriveEnabled, openPicker: openGoogleDrivePicker } = useGoogleDrivePicker(); + + const handleGoogleDriveClick = async () => { + try { + const files = await openGoogleDrivePicker({ multiple: true }); + if (files.length > 0) { + onGoogleDriveSelect(files); + } + } catch (error) { + console.error('Failed to pick files from Google Drive:', error); + } + }; const buttonProps = { variant: (source: string) => activeSource === source ? 'filled' : 'subtle', @@ -67,15 +80,24 @@ const FileSourceButtons: React.FC = ({ diff --git a/frontend/src/components/hotkeys/HotkeyDisplay.tsx b/frontend/src/components/hotkeys/HotkeyDisplay.tsx index e240f987ea..1b5953a63e 100644 --- a/frontend/src/components/hotkeys/HotkeyDisplay.tsx +++ b/frontend/src/components/hotkeys/HotkeyDisplay.tsx @@ -55,4 +55,4 @@ export const HotkeyDisplay: React.FC = ({ binding, size = 's ); }; -export default HotkeyDisplay; \ No newline at end of file +export default HotkeyDisplay; diff --git a/frontend/src/components/layout/Workbench.tsx b/frontend/src/components/layout/Workbench.tsx index f0f9a542ea..62fc48667c 100644 --- a/frontend/src/components/layout/Workbench.tsx +++ b/frontend/src/components/layout/Workbench.tsx @@ -1,9 +1,11 @@ +import React from 'react'; import { Box } from '@mantine/core'; import { useRainbowThemeContext } from '../shared/RainbowThemeProvider'; import { useToolWorkflow } from '../../contexts/ToolWorkflowContext'; import { useFileHandler } from '../../hooks/useFileHandler'; import { useFileState } from '../../contexts/FileContext'; import { useNavigationState, useNavigationActions } from '../../contexts/NavigationContext'; +import { useViewer } from '../../contexts/ViewerContext'; import './Workbench.css'; import TopControls from '../shared/TopControls'; @@ -20,11 +22,11 @@ export default function Workbench() { const { isRainbowMode } = useRainbowThemeContext(); // Use context-based hooks to eliminate all prop drilling - const { state } = useFileState(); + const { selectors } = useFileState(); const { workbench: currentView } = useNavigationState(); const { actions: navActions } = useNavigationActions(); const setCurrentView = navActions.setWorkbench; - const activeFiles = state.files.ids; + const activeFiles = selectors.getFiles(); const { previewFile, pageEditorFunctions, @@ -44,6 +46,9 @@ export default function Workbench() { const selectedTool = selectedToolId ? toolRegistry[selectedToolId] : null; const { addFiles } = useFileHandler(); + // Get active file index from ViewerContext + const { activeFileIndex, setActiveFileIndex } = useViewer(); + const handlePreviewClose = () => { setPreviewFile(null); const previousMode = sessionStorage.getItem('previousMode'); @@ -95,6 +100,8 @@ export default function Workbench() { setSidebarsVisible={setSidebarsVisible} previewFile={previewFile} onClose={handlePreviewClose} + activeFileIndex={activeFileIndex} + setActiveFileIndex={setActiveFileIndex} /> ); @@ -150,6 +157,12 @@ export default function Workbench() { { + const stub = selectors.getStirlingFileStub(f.fileId); + return { fileId: f.fileId, name: f.name, versionNumber: stub?.versionNumber }; + })} + currentFileIndex={activeFileIndex} + onFileSelect={setActiveFileIndex} /> )} @@ -161,7 +174,7 @@ export default function Workbench() { className="flex-1 min-h-0 relative z-10 workbench-scrollable " style={{ transition: 'opacity 0.15s ease-in-out', - paddingTop: activeFiles.length > 0 ? '3.5rem' : '0', + paddingTop: currentView === 'viewer' ? '0' : (activeFiles.length > 0 ? '3.5rem' : '0'), }} > {renderMainContent()} diff --git a/frontend/src/components/pageEditor/PageThumbnail.tsx b/frontend/src/components/pageEditor/PageThumbnail.tsx index 59e5819d9d..f6d77a3e45 100644 --- a/frontend/src/components/pageEditor/PageThumbnail.tsx +++ b/frontend/src/components/pageEditor/PageThumbnail.tsx @@ -1,5 +1,6 @@ -import React, { useCallback, useState, useEffect, useRef } from 'react'; -import { Text, Checkbox, Tooltip, ActionIcon } from '@mantine/core'; +import React, { useCallback, useState, useEffect, useRef, useMemo } from 'react'; +import { Text, Checkbox } from '@mantine/core'; +import { useMediaQuery } from '@mantine/hooks'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; import RotateLeftIcon from '@mui/icons-material/RotateLeft'; @@ -12,6 +13,7 @@ import { PDFPage, PDFDocument } from '../../types/pageEditor'; import { useThumbnailGeneration } from '../../hooks/useThumbnailGeneration'; import { useFilesModalContext } from '../../contexts/FilesModalContext'; import styles from './PageEditor.module.css'; +import HoverActionMenu, { HoverAction } from '../shared/HoverActionMenu'; interface PageThumbnailProps { @@ -65,6 +67,8 @@ const PageThumbnail: React.FC = ({ const [isDragging, setIsDragging] = useState(false); const [isMouseDown, setIsMouseDown] = useState(false); const [mouseStartPos, setMouseStartPos] = useState<{x: number, y: number} | null>(null); + const [isHovered, setIsHovered] = useState(false); + const isMobile = useMediaQuery('(max-width: 1024px)'); const dragElementRef = useRef(null); const [thumbnailUrl, setThumbnailUrl] = useState(page.thumbnail); const { getThumbnailFromCache, requestThumbnail } = useThumbnailGeneration(); @@ -270,8 +274,75 @@ const PageThumbnail: React.FC = ({ const handleMouseLeave = useCallback(() => { setIsMouseDown(false); setMouseStartPos(null); + setIsHovered(false); }, []); + // Build hover menu actions + const hoverActions = useMemo(() => [ + { + id: 'move-left', + icon: , + label: 'Move Left', + onClick: (e) => { + e.stopPropagation(); + if (index > 0 && !movingPage && !isAnimating) { + onSetMovingPage(page.pageNumber); + onReorderPages(page.pageNumber, index - 1); + setTimeout(() => onSetMovingPage(null), 650); + onSetStatus(`Moved page ${page.pageNumber} left`); + } + }, + disabled: index === 0 + }, + { + id: 'move-right', + icon: , + label: 'Move Right', + onClick: (e) => { + e.stopPropagation(); + if (index < totalPages - 1 && !movingPage && !isAnimating) { + onSetMovingPage(page.pageNumber); + onReorderPages(page.pageNumber, index + 1); + setTimeout(() => onSetMovingPage(null), 650); + onSetStatus(`Moved page ${page.pageNumber} right`); + } + }, + disabled: index === totalPages - 1 + }, + { + id: 'rotate-left', + icon: , + label: 'Rotate Left', + onClick: handleRotateLeft, + }, + { + id: 'rotate-right', + icon: , + label: 'Rotate Right', + onClick: handleRotateRight, + }, + { + id: 'delete', + icon: , + label: 'Delete Page', + onClick: handleDelete, + color: 'red', + }, + { + id: 'split', + icon: , + label: 'Split After', + onClick: handleSplit, + hidden: index >= totalPages - 1, + }, + { + id: 'insert', + icon: , + label: 'Insert File After', + onClick: handleInsertFileAfter, + } + ], [index, totalPages, movingPage, isAnimating, page.pageNumber, handleRotateLeft, handleRotateRight, handleDelete, handleSplit, handleInsertFileAfter, onReorderPages, onSetMovingPage, onSetStatus]); + return (
= ({ draggable={false} onMouseDown={handleMouseDown} onMouseUp={handleMouseUp} + onMouseEnter={() => setIsHovered(true)} onMouseLeave={handleMouseLeave} > { @@ -413,128 +485,12 @@ const PageThumbnail: React.FC = ({ {page.pageNumber} -
e.stopPropagation()} - onMouseUp={(e) => e.stopPropagation()} - onClick={(e) => e.stopPropagation()} - > - - { - e.stopPropagation(); - if (index > 0 && !movingPage && !isAnimating) { - onSetMovingPage(page.pageNumber); - // Actually move the page left (swap with previous page) - onReorderPages(page.pageNumber, index - 1); - setTimeout(() => onSetMovingPage(null), 650); - onSetStatus(`Moved page ${page.pageNumber} left`); - } - }} - > - - - - - - { - e.stopPropagation(); - if (index < totalPages - 1 && !movingPage && !isAnimating) { - onSetMovingPage(page.pageNumber); - // Actually move the page right (swap with next page) - onReorderPages(page.pageNumber, index + 1); - setTimeout(() => onSetMovingPage(null), 650); - onSetStatus(`Moved page ${page.pageNumber} right`); - } - }} - > - - - - - - - - - - - - - - - - - - - - - - - {index < totalPages - 1 && ( - - - - - - )} - - - - - - -
+ />
diff --git a/frontend/src/components/shared/AppConfigModal.tsx b/frontend/src/components/shared/AppConfigModal.tsx index 2ed7f71b71..1605471800 100644 --- a/frontend/src/components/shared/AppConfigModal.tsx +++ b/frontend/src/components/shared/AppConfigModal.tsx @@ -6,6 +6,7 @@ import Overview from './config/configSections/Overview'; import { createConfigNavSections } from './config/configNavSections'; import { NavKey } from './config/types'; import './AppConfigModal.css'; +import { Z_INDEX_OVER_FULLSCREEN_SURFACE } from '../../styles/zIndex'; interface AppConfigModalProps { opened: boolean; @@ -77,7 +78,7 @@ const AppConfigModal: React.FC = ({ opened, onClose }) => { centered radius="lg" withCloseButton={false} - style={{ zIndex: 1000 }} + zIndex={Z_INDEX_OVER_FULLSCREEN_SURFACE} overlayProps={{ opacity: 0.35, blur: 2 }} padding={0} fullScreen={isMobile} diff --git a/frontend/src/components/shared/Badge.tsx b/frontend/src/components/shared/Badge.tsx new file mode 100644 index 0000000000..7bf3283e4f --- /dev/null +++ b/frontend/src/components/shared/Badge.tsx @@ -0,0 +1,101 @@ +import React from 'react'; +import { Box } from '@mantine/core'; + +interface BadgeProps { + children: React.ReactNode; + size?: 'sm' | 'md' | 'lg'; + variant?: 'default' | 'colored'; + color?: string; + textColor?: string; + backgroundColor?: string; + className?: string; + style?: React.CSSProperties; +} + +const Badge: React.FC = ({ + children, + size = 'sm', + variant = 'default', + color, + textColor, + backgroundColor, + className, + style +}) => { + const getSizeStyles = () => { + switch (size) { + case 'sm': + return { + padding: '0.125rem 0.5rem', + fontSize: '0.75rem', + fontWeight: 700, + borderRadius: '0.5rem', + }; + case 'md': + return { + padding: '0.25rem 0.75rem', + fontSize: '0.875rem', + fontWeight: 700, + borderRadius: '0.625rem', + }; + case 'lg': + return { + padding: '0.375rem 1rem', + fontSize: '1rem', + fontWeight: 700, + borderRadius: '0.75rem', + }; + default: + return {}; + } + }; + + const getVariantStyles = () => { + // If explicit colors are provided, use them + if (textColor && backgroundColor) { + return { + backgroundColor, + color: textColor, + }; + } + + // If a single color is provided, use it for text and 20% opacity for background + if (color) { + return { + backgroundColor: `color-mix(in srgb, ${color} 20%, transparent)`, + color: color, + }; + } + + // If variant is colored but no color provided, use default colored styling + if (variant === 'colored') { + return { + backgroundColor: `color-mix(in srgb, var(--category-color-default) 15%, transparent)`, + color: 'var(--category-color-default)', + borderColor: `color-mix(in srgb, var(--category-color-default) 30%, transparent)`, + border: '1px solid', + }; + } + + // Default styling + return { + background: 'var(--tool-header-badge-bg)', + color: 'var(--tool-header-badge-text)', + }; + }; + + return ( + + {children} + + ); +}; + +export default Badge; diff --git a/frontend/src/components/shared/FileDropdownMenu.tsx b/frontend/src/components/shared/FileDropdownMenu.tsx new file mode 100644 index 0000000000..b03995fc7c --- /dev/null +++ b/frontend/src/components/shared/FileDropdownMenu.tsx @@ -0,0 +1,78 @@ +import React from 'react'; +import { Menu, Loader, Group, Text } from '@mantine/core'; +import VisibilityIcon from '@mui/icons-material/Visibility'; +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; +import FitText from './FitText'; + +interface FileDropdownMenuProps { + displayName: string; + activeFiles: Array<{ fileId: string; name: string; versionNumber?: number }>; + currentFileIndex: number; + onFileSelect?: (index: number) => void; + switchingTo?: string | null; + viewOptionStyle: React.CSSProperties; + pillRef?: React.RefObject; +} + +export const FileDropdownMenu: React.FC = ({ + displayName, + activeFiles, + currentFileIndex, + onFileSelect, + switchingTo, + viewOptionStyle, +}) => { + return ( + + +
+ {switchingTo === "viewer" ? ( + + ) : ( + + )} + + +
+
+ + {activeFiles.map((file, index) => { + const itemName = file?.name || 'Untitled'; + const isActive = index === currentFileIndex; + return ( + { + e.stopPropagation(); + onFileSelect?.(index); + }} + className="viewer-file-tab" + {...(isActive && { 'data-active': true })} + style={{ + justifyContent: 'flex-start', + }} + > + +
+ +
+ {file.versionNumber && file.versionNumber > 1 && ( + + v{file.versionNumber} + + )} +
+
+ ); + })} +
+
+ ); +}; diff --git a/frontend/src/components/shared/FilePickerModal.tsx b/frontend/src/components/shared/FilePickerModal.tsx index 9bb2694a1e..bb571b5897 100644 --- a/frontend/src/components/shared/FilePickerModal.tsx +++ b/frontend/src/components/shared/FilePickerModal.tsx @@ -125,6 +125,7 @@ const FilePickerModal = ({ title={t("fileUpload.selectFromStorage", "Select Files from Storage")} size="lg" scrollAreaComponent={ScrollArea.Autosize} + zIndex={1100} > {storedFiles.length === 0 ? ( diff --git a/frontend/src/components/shared/HoverActionMenu.module.css b/frontend/src/components/shared/HoverActionMenu.module.css new file mode 100644 index 0000000000..27974f152a --- /dev/null +++ b/frontend/src/components/shared/HoverActionMenu.module.css @@ -0,0 +1,28 @@ +/* Base Hover Menu */ +.hoverMenu { + position: absolute; + left: 50%; + transform: translateX(-50%); + display: flex; + gap: 8px; + align-items: center; + background: var(--bg-toolbar); + border: 1px solid var(--border-default); + padding: 6px 12px; + border-radius: 20px; + box-shadow: var(--shadow-md); + z-index: 30; + white-space: nowrap; + pointer-events: auto; + transition: opacity 0.2s ease-in-out; +} + +/* Inside positioning (Page Editor style) - within container */ +.inside { + bottom: 8px; +} + +/* Outside positioning (File Editor style) - below container */ +.outside { + bottom: -8px; +} diff --git a/frontend/src/components/shared/HoverActionMenu.tsx b/frontend/src/components/shared/HoverActionMenu.tsx new file mode 100644 index 0000000000..1c55f5f0ab --- /dev/null +++ b/frontend/src/components/shared/HoverActionMenu.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { ActionIcon, Tooltip } from '@mantine/core'; +import styles from './HoverActionMenu.module.css'; + +export interface HoverAction { + id: string; + icon: React.ReactNode; + label: string; + onClick: (e: React.MouseEvent) => void; + disabled?: boolean; + color?: string; + hidden?: boolean; +} + +interface HoverActionMenuProps { + show: boolean; + actions: HoverAction[]; + position?: 'inside' | 'outside'; + className?: string; +} + +const HoverActionMenu: React.FC = ({ + show, + actions, + position = 'inside', + className = '' +}) => { + const visibleActions = actions.filter(action => !action.hidden); + + if (visibleActions.length === 0) { + return null; + } + + return ( +
e.stopPropagation()} + onMouseUp={(e) => e.stopPropagation()} + onClick={(e) => e.stopPropagation()} + > + {visibleActions.map((action) => ( + + + {action.icon} + + + ))} +
+ ); +}; + +export default HoverActionMenu; diff --git a/frontend/src/components/shared/LanguageSelector.tsx b/frontend/src/components/shared/LanguageSelector.tsx index 0ea80a79fa..507dab16b0 100644 --- a/frontend/src/components/shared/LanguageSelector.tsx +++ b/frontend/src/components/shared/LanguageSelector.tsx @@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next'; import { supportedLanguages } from '../../i18n'; import LocalIcon from './LocalIcon'; import styles from './LanguageSelector.module.css'; +import { Z_INDEX_OVER_FULLSCREEN_SURFACE } from '../../styles/zIndex'; // Types interface LanguageSelectorProps { @@ -209,6 +210,7 @@ const LanguageSelector: React.FC = ({ position = 'bottom- width={600} position={position} offset={offset} + zIndex={Z_INDEX_OVER_FULLSCREEN_SURFACE} transitionProps={{ transition: 'scale-y', duration: 200, @@ -264,6 +266,7 @@ const LanguageSelector: React.FC = ({ position = 'bottom- boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)', backgroundColor: 'light-dark(var(--mantine-color-white), var(--mantine-color-dark-6))', border: 'light-dark(1px solid var(--mantine-color-gray-3), 1px solid var(--mantine-color-dark-4))', + zIndex: Z_INDEX_OVER_FULLSCREEN_SURFACE, }} > diff --git a/frontend/src/components/shared/QuickAccessBar.tsx b/frontend/src/components/shared/QuickAccessBar.tsx index af9c8acff6..7a8dd7fdd7 100644 --- a/frontend/src/components/shared/QuickAccessBar.tsx +++ b/frontend/src/components/shared/QuickAccessBar.tsx @@ -24,7 +24,7 @@ const QuickAccessBar = forwardRef((_, ref) => { const { t } = useTranslation(); const { isRainbowMode } = useRainbowThemeContext(); const { openFilesModal, isFilesModalOpen } = useFilesModalContext(); - const { handleReaderToggle, handleBackToTools, handleToolSelect, selectedToolKey, leftPanelView, toolRegistry, readerMode, resetTool } = useToolWorkflow(); + const { handleReaderToggle, handleToolSelect, selectedToolKey, leftPanelView, toolRegistry, readerMode, resetTool } = useToolWorkflow(); const { getToolNavigation } = useSidebarNavigation(); const { config } = useAppConfig(); const [configModalOpen, setConfigModalOpen] = useState(false); @@ -68,7 +68,8 @@ const QuickAccessBar = forwardRef((_, ref) => { onClick: (e: React.MouseEvent) => handleClick(e), 'aria-label': config.name } : { - onClick: () => handleClick() + onClick: () => handleClick(), + 'aria-label': config.name })} size={isActive ? (config.size || 'lg') : 'lg'} variant="subtle" @@ -98,12 +99,10 @@ const QuickAccessBar = forwardRef((_, ref) => { type: 'navigation', onClick: () => { setActiveButton('read'); - handleBackToTools(); handleReaderToggle(); } }, - // TODO: Add sign - //{ + // { // id: 'sign', // name: t("quickAccess.sign", "Sign"), // icon: , @@ -114,7 +113,7 @@ const QuickAccessBar = forwardRef((_, ref) => { // setActiveButton('sign'); // handleToolSelect('sign'); // } - //}, + // }, { id: 'automate', name: t("quickAccess.automate", "Automate"), @@ -230,6 +229,7 @@ const QuickAccessBar = forwardRef((_, ref) => { onClick={config.onClick} style={getNavButtonStyle(config, activeButton, isFilesModalOpen, configModalOpen, selectedToolKey, leftPanelView)} className={isNavButtonActive(config, activeButton, isFilesModalOpen, configModalOpen, selectedToolKey, leftPanelView) ? 'activeIconScale' : ''} + aria-label={config.name} data-testid={`${config.id}-button`} > diff --git a/frontend/src/components/shared/RainbowThemeProvider.tsx b/frontend/src/components/shared/RainbowThemeProvider.tsx index d8a7eb7654..18efba2565 100644 --- a/frontend/src/components/shared/RainbowThemeProvider.tsx +++ b/frontend/src/components/shared/RainbowThemeProvider.tsx @@ -6,9 +6,10 @@ import rainbowStyles from '../../styles/rainbow.module.css'; import { ToastProvider } from '../toast'; import ToastRenderer from '../toast/ToastRenderer'; import { ToastPortalBinder } from '../toast'; +import type { ThemeMode } from '../../constants/theme'; interface RainbowThemeContextType { - themeMode: 'light' | 'dark' | 'rainbow'; + themeMode: ThemeMode; isRainbowMode: boolean; isToggleDisabled: boolean; toggleTheme: () => void; diff --git a/frontend/src/components/shared/RightRail.tsx b/frontend/src/components/shared/RightRail.tsx index 2b4569dc51..44af1e215b 100644 --- a/frontend/src/components/shared/RightRail.tsx +++ b/frontend/src/components/shared/RightRail.tsx @@ -20,18 +20,23 @@ import ViewerAnnotationControls from './rightRail/ViewerAnnotationControls'; import { parseSelection } from '../../utils/bulkselection/parseSelection'; +import { useSidebarContext } from '../../contexts/SidebarContext'; + export default function RightRail() { + const { sidebarRefs } = useSidebarContext(); const { t } = useTranslation(); const [isPanning, setIsPanning] = useState(false); // Viewer context for PDF controls - safely handle when not available const viewerContext = React.useContext(ViewerContext); const { toggleTheme } = useRainbowThemeContext(); - const { buttons, actions } = useRightRail(); + const { buttons, actions, allButtonsDisabled } = useRightRail(); + const topButtons = useMemo(() => buttons.filter(b => (b.section || 'top') === 'top' && (b.visible ?? true)), [buttons]); // Access PageEditor functions for page-editor-specific actions - const { pageEditorFunctions } = useToolWorkflow(); + const { pageEditorFunctions, toolPanelMode, leftPanelView } = useToolWorkflow(); + const disableForFullscreen = toolPanelMode === 'fullscreen' && leftPanelView === 'toolPicker'; // CSV input state for page selection const [csvInput, setCsvInput] = useState(""); @@ -176,19 +181,19 @@ export default function RightRail() { }, [currentView]); return ( -
+
{topButtons.length > 0 && ( <>
{topButtons.map(btn => ( - + actions[btn.id]?.()} - disabled={btn.disabled} + disabled={btn.disabled || allButtonsDisabled || disableForFullscreen} > {btn.icon} @@ -206,7 +211,7 @@ export default function RightRail() { >
{/* Search */} - +
@@ -214,7 +219,7 @@ export default function RightRail() { variant="subtle" radius="md" className="right-rail-icon" - disabled={currentView !== 'viewer'} + disabled={currentView !== 'viewer' || allButtonsDisabled || disableForFullscreen} aria-label={typeof t === 'function' ? t('rightRail.search', 'Search PDF') : 'Search PDF'} > @@ -234,7 +239,7 @@ export default function RightRail() { {/* Pan Mode */} - + {/* Rotate Left */} - + { viewerContext?.rotationActions.rotateBackward(); }} - disabled={currentView !== 'viewer'} + disabled={currentView !== 'viewer' || allButtonsDisabled || disableForFullscreen} > {/* Rotate Right */} - + { viewerContext?.rotationActions.rotateForward(); }} - disabled={currentView !== 'viewer'} + disabled={currentView !== 'viewer' || allButtonsDisabled || disableForFullscreen} > {/* Sidebar Toggle */} - + { viewerContext?.toggleThumbnailSidebar(); }} - disabled={currentView !== 'viewer'} + disabled={currentView !== 'viewer' || allButtonsDisabled || disableForFullscreen} > {/* Annotation Controls */} - +
@@ -308,14 +316,14 @@ export default function RightRail() { >
{/* Select All Button */} - +
@@ -323,14 +331,14 @@ export default function RightRail() { {/* Deselect All Button */} - +
@@ -339,7 +347,7 @@ export default function RightRail() { {/* Select by Numbers - page editor only, with animated presence */} {pageControlsMounted && ( - +
@@ -349,7 +357,7 @@ export default function RightRail() { variant="subtle" radius="md" className="right-rail-icon" - disabled={!pageControlsVisible || totalItems === 0} + disabled={!pageControlsVisible || totalItems === 0 || allButtonsDisabled || disableForFullscreen} aria-label={typeof t === 'function' ? t('rightRail.selectByNumber', 'Select by Page Numbers') : 'Select by Page Numbers'} > @@ -376,7 +384,7 @@ export default function RightRail() { {/* Delete Selected Pages - page editor only, with animated presence */} {pageControlsMounted && ( - +
@@ -385,7 +393,7 @@ export default function RightRail() { radius="md" className="right-rail-icon" onClick={() => { pageEditorFunctions?.handleDelete?.(); }} - disabled={!pageControlsVisible || (pageEditorFunctions?.selectedPageIds?.length || 0) === 0} + disabled={!pageControlsVisible || (pageEditorFunctions?.selectedPageIds?.length || 0) === 0 || allButtonsDisabled || disableForFullscreen} aria-label={typeof t === 'function' ? t('rightRail.deleteSelected', 'Delete Selected Pages') : 'Delete Selected Pages'} > @@ -398,7 +406,7 @@ export default function RightRail() { {/* Export Selected Pages - page editor only */} {pageControlsMounted && ( - +
{ pageEditorFunctions?.onExportSelected?.(); }} - disabled={!pageControlsVisible || (pageEditorFunctions?.selectedPageIds?.length || 0) === 0 || pageEditorFunctions?.exportLoading} + disabled={!pageControlsVisible || (pageEditorFunctions?.selectedPageIds?.length || 0) === 0 || pageEditorFunctions?.exportLoading || allButtonsDisabled || disableForFullscreen} aria-label={typeof t === 'function' ? t('rightRail.exportSelected', 'Export Selected Pages') : 'Export Selected Pages'} > @@ -417,7 +425,7 @@ export default function RightRail() { )} {/* Close (File Editor: Close Selected | Page Editor: Close PDF) */} - +
@@ -441,7 +450,8 @@ export default function RightRail() { {/* Theme toggle and Language dropdown */}
- + - + +
+ +
+
0 ? t('rightRail.downloadSelected', 'Download Selected Files') : t('rightRail.downloadAll', 'Download All')) - } position="left" offset={12} arrow> + } position="left" offset={12} arrow portalTarget={document.body}>
@@ -481,4 +495,3 @@ export default function RightRail() { ); } - diff --git a/frontend/src/components/shared/Tooltip.tsx b/frontend/src/components/shared/Tooltip.tsx index 543422609b..775cbbee13 100644 --- a/frontend/src/components/shared/Tooltip.tsx +++ b/frontend/src/components/shared/Tooltip.tsx @@ -8,6 +8,7 @@ import { TooltipContent } from './tooltip/TooltipContent'; import { useSidebarContext } from '../../contexts/SidebarContext'; import { BASE_PATH } from '../../constants/app'; import styles from './tooltip/Tooltip.module.css'; +import { Z_INDEX_OVER_FULLSCREEN_SURFACE } from '../../styles/zIndex'; export interface TooltipProps { sidebarTooltip?: boolean; @@ -28,6 +29,10 @@ export interface TooltipProps { pinOnClick?: boolean; /** If true, clicking outside also closes when not pinned (default true) */ closeOnOutside?: boolean; + /** If true, tooltip interaction is disabled entirely */ + disabled?: boolean; + /** If false, tooltip will not open on focus (hover only) */ + openOnFocus?: boolean; } export const Tooltip: React.FC = ({ @@ -48,6 +53,8 @@ export const Tooltip: React.FC = ({ containerStyle = {}, pinOnClick = false, closeOnOutside = true, + disabled = false, + openOnFocus = true, }) => { const [internalOpen, setInternalOpen] = useState(false); const [isPinned, setIsPinned] = useState(false); @@ -68,7 +75,7 @@ export const Tooltip: React.FC = ({ const sidebarContext = sidebarTooltip ? useSidebarContext() : null; const isControlled = controlledOpen !== undefined; - const open = isControlled ? !!controlledOpen : internalOpen; + const open = (isControlled ? !!controlledOpen : internalOpen) && !disabled; const setOpen = useCallback( (newOpen: boolean) => { @@ -148,15 +155,16 @@ export const Tooltip: React.FC = ({ // === Trigger handlers === const openWithDelay = useCallback(() => { clearTimers(); + if (disabled) return; openTimeoutRef.current = setTimeout(() => setOpen(true), Math.max(0, delay || 0)); - }, [clearTimers, setOpen, delay]); + }, [clearTimers, setOpen, delay, disabled]); const handlePointerEnter = useCallback( (e: React.PointerEvent) => { - if (!isPinned) openWithDelay(); + if (!isPinned && !disabled) openWithDelay(); (children.props as any)?.onPointerEnter?.(e); }, - [isPinned, openWithDelay, children.props] + [isPinned, openWithDelay, children.props, disabled] ); const handlePointerLeave = useCallback( @@ -219,10 +227,10 @@ export const Tooltip: React.FC = ({ // Keyboard / focus accessibility const handleFocus = useCallback( (e: React.FocusEvent) => { - if (!isPinned) openWithDelay(); + if (!isPinned && !disabled && openOnFocus) openWithDelay(); (children.props as any)?.onFocus?.(e); }, - [isPinned, openWithDelay, children.props] + [isPinned, openWithDelay, children.props, disabled, openOnFocus] ); const handleBlur = useCallback( @@ -291,7 +299,7 @@ export const Tooltip: React.FC = ({ left: coords.left, width: maxWidth !== undefined ? maxWidth : (sidebarTooltip ? '25rem' as const : undefined), minWidth, - zIndex: 9999, + zIndex: Z_INDEX_OVER_FULLSCREEN_SURFACE, visibility: positionReady ? 'visible' : 'hidden', opacity: positionReady ? 1 : 0, color: 'var(--text-primary)', @@ -345,9 +353,13 @@ export const Tooltip: React.FC = ({ return ( <> {childWithHandlers} - {portalTarget && document.body.contains(portalTarget) - ? tooltipElement && createPortal(tooltipElement, portalTarget) - : tooltipElement} + {(() => { + const defaultTarget = typeof document !== 'undefined' ? document.body : null; + const target = portalTarget ?? defaultTarget; + return tooltipElement && target + ? createPortal(tooltipElement, target) + : tooltipElement; + })()} ); }; diff --git a/frontend/src/components/shared/TopControls.tsx b/frontend/src/components/shared/TopControls.tsx index 8edae6e72b..73ae393119 100644 --- a/frontend/src/components/shared/TopControls.tsx +++ b/frontend/src/components/shared/TopControls.tsx @@ -6,9 +6,10 @@ import VisibilityIcon from "@mui/icons-material/Visibility"; import EditNoteIcon from "@mui/icons-material/EditNote"; import FolderIcon from "@mui/icons-material/Folder"; import { WorkbenchType, isValidWorkbench } from '../../types/workbench'; +import { FileDropdownMenu } from './FileDropdownMenu'; -const viewOptionStyle = { +const viewOptionStyle: React.CSSProperties = { display: 'inline-flex', flexDirection: 'row', alignItems: 'center', @@ -19,16 +20,38 @@ const viewOptionStyle = { // Build view options showing text always -const createViewOptions = (currentView: WorkbenchType, switchingTo: WorkbenchType | null) => { +const createViewOptions = ( + currentView: WorkbenchType, + switchingTo: WorkbenchType | null, + activeFiles: Array<{ fileId: string; name: string; versionNumber?: number }>, + currentFileIndex: number, + onFileSelect?: (index: number) => void +) => { + const currentFile = activeFiles[currentFileIndex]; + const isInViewer = currentView === 'viewer'; + const fileName = currentFile?.name || ''; + const displayName = isInViewer && fileName ? fileName : 'Viewer'; + const hasMultipleFiles = activeFiles.length > 1; + const showDropdown = isInViewer && hasMultipleFiles; + const viewerOption = { - label: ( -
+ label: showDropdown ? ( + + ) : ( +
{switchingTo === "viewer" ? ( ) : ( )} - Viewer + {displayName}
), value: "viewer", @@ -36,7 +59,7 @@ const createViewOptions = (currentView: WorkbenchType, switchingTo: WorkbenchTyp const pageEditorOption = { label: ( -
+
{currentView === "pageEditor" ? ( <> {switchingTo === "pageEditor" ? : } @@ -55,7 +78,7 @@ const createViewOptions = (currentView: WorkbenchType, switchingTo: WorkbenchTyp const fileEditorOption = { label: ( -
+
{currentView === "fileEditor" ? ( <> {switchingTo === "fileEditor" ? : } @@ -83,12 +106,18 @@ const createViewOptions = (currentView: WorkbenchType, switchingTo: WorkbenchTyp interface TopControlsProps { currentView: WorkbenchType; setCurrentView: (view: WorkbenchType) => void; + activeFiles?: Array<{ fileId: string; name: string; versionNumber?: number }>; + currentFileIndex?: number; + onFileSelect?: (index: number) => void; } const TopControls = ({ currentView, setCurrentView, - }: TopControlsProps) => { + activeFiles = [], + currentFileIndex = 0, + onFileSelect, +}: TopControlsProps) => { const { isRainbowMode } = useRainbowThemeContext(); const [switchingTo, setSwitchingTo] = useState(null); @@ -118,7 +147,7 @@ const TopControls = ({
{ +
+
+ + {t('settings.general.defaultToolPickerMode', 'Default tool picker mode')} + + + {t('settings.general.defaultToolPickerModeDescription', 'Choose whether the tool picker opens in fullscreen or sidebar by default')} + +
+ updatePreference('defaultToolPanelMode', val as ToolPanelMode)} + data={[ + { label: t('settings.general.mode.sidebar', 'Sidebar'), value: 'sidebar' }, + { label: t('settings.general.mode.fullscreen', 'Fullscreen'), value: 'fullscreen' }, + ]} + /> +
{ const { t } = useTranslation(); const { toolRegistry } = useToolWorkflow(); const { hotkeys, defaults, updateHotkey, resetHotkey, pauseHotkeys, resumeHotkeys, getDisplayParts, isMac } = useHotkeys(); - const [editingTool, setEditingTool] = useState(null); + const [editingTool, setEditingTool] = useState(null); const [error, setError] = useState(null); + const [searchQuery, setSearchQuery] = useState(''); - const tools = useMemo(() => Object.entries(toolRegistry), [toolRegistry]); + const tools = useMemo(() => Object.entries(toolRegistry) as [ToolId, ToolRegistryEntry][], [toolRegistry]); + + const filteredTools = useMemo(() => { + if (!searchQuery.trim()) return tools; + + const query = searchQuery.toLowerCase(); + return tools.filter(([toolId, tool]) => + tool.name.toLowerCase().includes(query) || + tool.description.toLowerCase().includes(query) || + toolId.toLowerCase().includes(query) + ); + }, [tools, searchQuery]); useEffect(() => { if (!editingTool) { @@ -45,15 +59,17 @@ const HotkeysSection: React.FC = () => { } const handleKeyDown = (event: KeyboardEvent) => { - event.preventDefault(); - event.stopPropagation(); - if (event.key === 'Escape') { + event.preventDefault(); + event.stopPropagation(); setEditingTool(null); setError(null); return; } + event.preventDefault(); + event.stopPropagation(); + const binding = eventToBinding(event as KeyboardEvent); if (!binding) { const osKey = isMac ? 'mac' : 'windows'; @@ -64,12 +80,15 @@ const HotkeysSection: React.FC = () => { return; } - const conflictEntry = Object.entries(hotkeys).find(([toolId, existing]) => ( + const conflictEntry = (Object.entries(hotkeys) as [ToolId, HotkeyBinding][]).find(([toolId, existing]) => ( toolId !== editingTool && bindingEquals(existing, binding) )); if (conflictEntry) { - const conflictTool = toolRegistry[conflictEntry[0]]?.name ?? conflictEntry[0]; + const conflictKey = conflictEntry[0]; + const conflictTool = (conflictKey in toolRegistry) + ? toolRegistry[conflictKey as ToolId]?.name + : conflictKey; setError(t('settings.hotkeys.errorConflict', 'Shortcut already used by {{tool}}.', { tool: conflictTool })); return; } @@ -85,7 +104,7 @@ const HotkeysSection: React.FC = () => { }; }, [editingTool, hotkeys, toolRegistry, updateHotkey, t]); - const handleStartCapture = (toolId: string) => { + const handleStartCapture = (toolId: ToolId) => { setEditingTool(toolId); setError(null); }; @@ -99,9 +118,22 @@ const HotkeysSection: React.FC = () => {
+ setSearchQuery(event.currentTarget.value)} + size="md" + radius="md" + /> + - {tools.map(([toolId, tool], index) => { + {filteredTools.length === 0 ? ( + + {t('toolPicker.noToolsFound', 'No tools found')} + + ) : ( + filteredTools.map(([toolId, tool], index) => { const currentBinding = hotkeys[toolId]; const defaultBinding = defaults[toolId]; const isEditing = editingTool === toolId; @@ -158,14 +190,15 @@ const HotkeysSection: React.FC = () => { )} - {index < tools.length - 1 && } + {index < filteredTools.length - 1 && } ); - })} + }) + )} ); }; -export default HotkeysSection; \ No newline at end of file +export default HotkeysSection; diff --git a/frontend/src/components/shared/config/configSections/Overview.tsx b/frontend/src/components/shared/config/configSections/Overview.tsx index e591655e0c..d3f250f49c 100644 --- a/frontend/src/components/shared/config/configSections/Overview.tsx +++ b/frontend/src/components/shared/config/configSections/Overview.tsx @@ -51,7 +51,6 @@ const Overview: React.FC = () => { } : null; const integrationConfig = config ? { - GoogleDriveEnabled: config.GoogleDriveEnabled, SSOAutoLogin: config.SSOAutoLogin, } : null; diff --git a/frontend/src/components/shared/rightRail/RightRail.css b/frontend/src/components/shared/rightRail/RightRail.css index d310edc408..94b1f551c0 100644 --- a/frontend/src/components/shared/rightRail/RightRail.css +++ b/frontend/src/components/shared/rightRail/RightRail.css @@ -46,6 +46,13 @@ background-color: transparent !important; } +/* When all buttons are disabled via context */ +.right-rail--all-disabled .right-rail-icon { + color: var(--right-rail-icon-disabled) !important; + background-color: transparent !important; + pointer-events: none !important; +} + .right-rail-spacer { flex: 1; } diff --git a/frontend/src/components/shared/rightRail/ViewerAnnotationControls.tsx b/frontend/src/components/shared/rightRail/ViewerAnnotationControls.tsx index 92b778e44c..0ffa3102e8 100644 --- a/frontend/src/components/shared/rightRail/ViewerAnnotationControls.tsx +++ b/frontend/src/components/shared/rightRail/ViewerAnnotationControls.tsx @@ -10,12 +10,14 @@ import { useFileState, useFileContext } from '../../../contexts/FileContext'; import { generateThumbnailWithMetadata } from '../../../utils/thumbnailUtils'; import { createProcessedFile } from '../../../contexts/file/fileActions'; import { createStirlingFile, createNewStirlingFileStub } from '../../../types/fileContext'; +import { useNavigationState } from '../../../contexts/NavigationContext'; interface ViewerAnnotationControlsProps { currentView: string; + disabled?: boolean; } -export default function ViewerAnnotationControls({ currentView }: ViewerAnnotationControlsProps) { +export default function ViewerAnnotationControls({ currentView, disabled = false }: ViewerAnnotationControlsProps) { const { t } = useTranslation(); const [selectedColor, setSelectedColor] = useState('#000000'); const [isColorPickerOpen, setIsColorPickerOpen] = useState(false); @@ -25,13 +27,17 @@ export default function ViewerAnnotationControls({ currentView }: ViewerAnnotati const viewerContext = React.useContext(ViewerContext); // Signature context for accessing drawing API - const { signatureApiRef } = useSignature(); + const { signatureApiRef, isPlacementMode } = useSignature(); // File state for save functionality const { state, selectors } = useFileState(); const { actions: fileActions } = useFileContext(); const activeFiles = selectors.getFiles(); + // Check if we're in sign mode + const { selectedTool } = useNavigationState(); + const isSignMode = selectedTool === 'sign'; + // Turn off annotation mode when switching away from viewer useEffect(() => { if (currentView !== 'viewer' && viewerContext?.isAnnotationMode) { @@ -39,10 +45,15 @@ export default function ViewerAnnotationControls({ currentView }: ViewerAnnotati } }, [currentView, viewerContext]); + // Don't show any annotation controls in sign mode + if (isSignMode) { + return null; + } + return ( <> {/* Annotation Visibility Toggle */} - + { viewerContext?.toggleAnnotationsVisibility(); }} - disabled={currentView !== 'viewer' || viewerContext?.isAnnotationMode} + disabled={disabled || currentView !== 'viewer' || viewerContext?.isAnnotationMode || isPlacementMode} > @@ -119,7 +130,7 @@ export default function ViewerAnnotationControls({ currentView }: ViewerAnnotati
) : ( // When inactive: Show "Draw" tooltip - + @@ -145,7 +156,7 @@ export default function ViewerAnnotationControls({ currentView }: ViewerAnnotati )} {/* Save PDF with Annotations */} - + diff --git a/frontend/src/components/tools/FullscreenToolList.tsx b/frontend/src/components/tools/FullscreenToolList.tsx new file mode 100644 index 0000000000..ce76b73751 --- /dev/null +++ b/frontend/src/components/tools/FullscreenToolList.tsx @@ -0,0 +1,253 @@ +import React, { useMemo } from 'react'; +import { Text } from '@mantine/core'; +import { useTranslation } from 'react-i18next'; +import { ToolRegistryEntry, getSubcategoryLabel, getSubcategoryColor, getSubcategoryIcon } from '../../data/toolsTaxonomy'; +import { ToolId } from '../../types/toolId'; +import { useToolSections } from '../../hooks/useToolSections'; +import NoToolsFound from './shared/NoToolsFound'; +import { useToolWorkflow } from '../../contexts/ToolWorkflowContext'; +import StarRoundedIcon from '@mui/icons-material/StarRounded'; +import ThumbUpRoundedIcon from '@mui/icons-material/ThumbUpRounded'; +import Badge from '../shared/Badge'; +import './ToolPanel.css'; +import DetailedToolItem from './fullscreen/DetailedToolItem'; +import CompactToolItem from './fullscreen/CompactToolItem'; +import { useFavoriteToolItems } from '../../hooks/tools/useFavoriteToolItems'; + +interface FullscreenToolListProps { + filteredTools: Array<{ item: [ToolId, ToolRegistryEntry]; matchedText?: string }>; + searchQuery: string; + showDescriptions: boolean; + selectedToolKey: string | null; + matchedTextMap: Map; + onSelect: (id: ToolId) => void; +} + +const FullscreenToolList = ({ + filteredTools, + searchQuery, + showDescriptions, + selectedToolKey, + matchedTextMap: _matchedTextMap, + onSelect, +}: FullscreenToolListProps) => { + const { t } = useTranslation(); + const { toolRegistry, favoriteTools } = useToolWorkflow(); + + const { sections, searchGroups } = useToolSections(filteredTools, searchQuery); + + const tooltipPortalTarget = typeof document !== 'undefined' ? document.body : undefined; + + + const favoriteToolItems = useFavoriteToolItems(favoriteTools, toolRegistry); + + const quickSection = useMemo(() => sections.find(section => section.key === 'quick'), [sections]); + const recommendedItems = useMemo(() => { + if (!quickSection) return [] as Array<{ id: string, tool: ToolRegistryEntry }>; + const items: Array<{ id: string, tool: ToolRegistryEntry }> = []; + quickSection.subcategories.forEach(sc => sc.tools.forEach(t => items.push(t))); + return items; + }, [quickSection]); + + // Show recommended/favorites section only when not searching + const showRecentFavorites = searchQuery.trim().length === 0 && ((recommendedItems.length > 0) || favoriteToolItems.length > 0); + + const subcategoryGroups = useMemo(() => { + if (searchQuery.trim().length > 0) { + return searchGroups; + } + const allSection = sections.find(section => section.key === 'all'); + return allSection ? allSection.subcategories : []; + }, [searchGroups, sections, searchQuery]); + + if (subcategoryGroups.length === 0 && !showRecentFavorites) { + return ( +
+ + + {t('toolPanel.fullscreen.noResults', 'Try adjusting your search or toggle descriptions to find what you need.')} + +
+ ); + } + + const containerClass = showDescriptions + ? 'tool-panel__fullscreen-groups tool-panel__fullscreen-groups--detailed' + : 'tool-panel__fullscreen-groups tool-panel__fullscreen-groups--compact'; + + // Helper function to render a tool item + const renderToolItem = (id: ToolId, tool: ToolRegistryEntry) => { + const isSelected = selectedToolKey === id; + + const handleClick = () => { + if (!tool.component && !tool.link && id !== 'read' && id !== 'multiTool') return; + if (tool.link) { + window.open(tool.link, '_blank', 'noopener,noreferrer'); + return; + } + onSelect(id as ToolId); + }; + + if (showDescriptions) { + return ( + + ); + } + + return ( + + ); + }; + + return ( +
+ {showRecentFavorites && ( + <> + {favoriteToolItems.length > 0 && ( +
+
+
+ + + + + {t('toolPanel.fullscreen.favorites', 'Favourites')} + +
+ + {favoriteToolItems.length} + +
+ {showDescriptions ? ( +
+ {favoriteToolItems.map((item) => item && renderToolItem(item.id, item.tool))} +
+ ) : ( +
+ {favoriteToolItems.map((item) => item && renderToolItem(item.id, item.tool))} +
+ )} +
+ )} + + {recommendedItems.length > 0 && ( +
+
+
+ + + + + {t('toolPanel.fullscreen.recommended', 'Recommended')} + +
+ + {recommendedItems.length} + +
+ {showDescriptions ? ( +
+ {recommendedItems.map((item: any) => renderToolItem(item.id, item.tool))} +
+ ) : ( +
+ {recommendedItems.map((item: any) => renderToolItem(item.id, item.tool))} +
+ )} +
+ )} + + )} + + {subcategoryGroups.map(({ subcategoryId, tools }) => { + const categoryColor = getSubcategoryColor(subcategoryId); + + return ( +
+
+
+ + {getSubcategoryIcon(subcategoryId)} + + + {getSubcategoryLabel(t, subcategoryId)} + +
+ + {tools.length} + +
+ + {showDescriptions ? ( +
+ {tools.map(({ id, tool }) => renderToolItem(id as ToolId, tool))} +
+ ) : ( +
+ {tools.map(({ id, tool }) => renderToolItem(id as ToolId, tool))} +
+ )} +
+ ); + })} +
+ ); +}; + +export default FullscreenToolList; + + diff --git a/frontend/src/components/tools/FullscreenToolSurface.tsx b/frontend/src/components/tools/FullscreenToolSurface.tsx new file mode 100644 index 0000000000..69b3c205de --- /dev/null +++ b/frontend/src/components/tools/FullscreenToolSurface.tsx @@ -0,0 +1,159 @@ +import { useState, useRef } from 'react'; +import { ActionIcon, ScrollArea, Switch, useMantineColorScheme } from '@mantine/core'; +import DoubleArrowIcon from '@mui/icons-material/DoubleArrow'; +import { useTranslation } from 'react-i18next'; +import ToolSearch from './toolPicker/ToolSearch'; +import FullscreenToolList from './FullscreenToolList'; +import { ToolRegistryEntry } from '../../data/toolsTaxonomy'; +import { ToolId } from '../../types/toolId'; +import { useFocusTrap } from '../../hooks/useFocusTrap'; +import { BASE_PATH } from '../../constants/app'; +import { Tooltip } from '../shared/Tooltip'; +import './ToolPanel.css'; +import { ToolPanelGeometry } from '../../hooks/tools/useToolPanelGeometry'; +import { Z_INDEX_OVER_FULLSCREEN_SURFACE } from '../../styles/zIndex'; + +interface FullscreenToolSurfaceProps { + searchQuery: string; + toolRegistry: Partial>; + filteredTools: Array<{ item: [ToolId, ToolRegistryEntry]; matchedText?: string }>; + selectedToolKey: string | null; + showDescriptions: boolean; + matchedTextMap: Map; + onSearchChange: (value: string) => void; + onSelect: (id: ToolId) => void; + onToggleDescriptions: () => void; + onExitFullscreenMode: () => void; + toggleLabel: string; + geometry: ToolPanelGeometry | null; +} + +const FullscreenToolSurface = ({ + searchQuery, + toolRegistry, + filteredTools, + selectedToolKey, + showDescriptions, + matchedTextMap, + onSearchChange, + onSelect, + onToggleDescriptions, + onExitFullscreenMode, + toggleLabel, + geometry, +}: FullscreenToolSurfaceProps) => { + const { t } = useTranslation(); + const { colorScheme } = useMantineColorScheme(); + const [isExiting, setIsExiting] = useState(false); + const surfaceRef = useRef(null); + + // Enable focus trap when surface is active + useFocusTrap(surfaceRef, !isExiting); + + const brandAltText = t("home.mobile.brandAlt", "Stirling PDF logo"); + const brandIconSrc = `${BASE_PATH}/branding/StirlingPDFLogoNoText${ + colorScheme === "dark" ? "Dark" : "Light" + }.svg`; + const brandTextSrc = `${BASE_PATH}/branding/StirlingPDFLogo${ + colorScheme === "dark" ? "White" : "Black" + }Text.svg`; + + const handleExit = () => { + const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; + + if (prefersReducedMotion) { + onExitFullscreenMode(); + return; + } + + setIsExiting(true); + const el = surfaceRef.current; + if (!el) { + onExitFullscreenMode(); + return; + } + // Rely on CSS animation end rather than duplicating timing in JS + el.addEventListener('animationend', () => { + onExitFullscreenMode(); + }, { once: true }); +}; + + + const style = geometry + ? { + left: `${geometry.left}px`, + top: `${geometry.top}px`, + width: `${geometry.width}px`, + height: `${geometry.height}px`, + } + : undefined; + + return ( +
+
+
+
+ + {brandAltText} +
+
+ + + + + +
+
+ +
+ + onToggleDescriptions()} + size="md" + labelPosition="left" + label={t('toolPanel.fullscreen.showDetails', 'Show Details')} + /> +
+ +
+ + + +
+
+
+ ); +}; + +export default FullscreenToolSurface; + + diff --git a/frontend/src/components/tools/SearchResults.tsx b/frontend/src/components/tools/SearchResults.tsx index 5bd6036cc5..5fef3d8584 100644 --- a/frontend/src/components/tools/SearchResults.tsx +++ b/frontend/src/components/tools/SearchResults.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { Box, Stack } from '@mantine/core'; import { getSubcategoryLabel, ToolRegistryEntry } from '../../data/toolsTaxonomy'; +import { ToolId } from '../../types/toolId'; import ToolButton from './toolPicker/ToolButton'; import { useTranslation } from 'react-i18next'; import { useToolSections } from '../../hooks/useToolSections'; @@ -9,7 +10,7 @@ import NoToolsFound from './shared/NoToolsFound'; import "./toolPicker/ToolPicker.css"; interface SearchResultsProps { - filteredTools: Array<{ item: [string, ToolRegistryEntry]; matchedText?: string }>; + filteredTools: Array<{ item: [ToolId, ToolRegistryEntry]; matchedText?: string }>; onSelect: (id: string) => void; searchQuery?: string; } @@ -40,13 +41,13 @@ const SearchResults: React.FC = ({ filteredTools, onSelect, {group.tools.map(({ id, tool }) => { const matchedText = matchedTextMap.get(id); // Check if the match was from synonyms and show the actual synonym that matched - const isSynonymMatch = matchedText && tool.synonyms?.some(synonym => + const isSynonymMatch = matchedText && tool.synonyms?.some(synonym => matchedText.toLowerCase().includes(synonym.toLowerCase()) ); - const matchedSynonym = isSynonymMatch ? tool.synonyms?.find(synonym => + const matchedSynonym = isSynonymMatch ? tool.synonyms?.find(synonym => matchedText.toLowerCase().includes(synonym.toLowerCase()) ) : undefined; - + return ( { + setAllRightRailButtonsDisabled(fullscreenExpanded); + }, [fullscreenExpanded, setAllRightRailButtonsDisabled]); + + const fullscreenGeometry = useToolPanelGeometry({ + enabled: fullscreenExpanded, + toolPanelRef, + quickAccessRef, + rightRailRef, + }); + + const toggleLabel = isFullscreenMode + ? t('toolPanel.toggle.sidebar', 'Switch to sidebar mode') + : t('toolPanel.toggle.fullscreen', 'Switch to fullscreen mode'); + + const handleModeToggle = () => { + const nextMode = isFullscreenMode ? 'sidebar' : 'fullscreen'; + setToolPanelMode(nextMode); + + if (nextMode === 'fullscreen' && leftPanelView !== 'toolPicker') { + setLeftPanelView('toolPicker'); + } + }; + + const computedWidth = () => { + if (isMobile) { + return '100%'; + } + + if (!isPanelVisible) { + return '0'; + } + + return '18.5rem'; + }; + + const matchedTextMap = useMemo(() => { + const map = new Map(); + filteredTools.forEach(({ item: [id], matchedText }) => { + if (matchedText) { + map.set(id, matchedText); + } + }); + return map; + }, [filteredTools]); return (
-
- {/* Search Bar - Always visible at the top */} + {!fullscreenExpanded && (
- -
- - {searchQuery.trim().length > 0 ? ( - // Searching view (replaces both picker and content) -
- handleToolSelect(id as ToolId)} - searchQuery={searchQuery} - /> -
- ) : leftPanelView === 'toolPicker' ? ( - // Tool Picker View -
- handleToolSelect(id as ToolId)} - filteredTools={filteredTools} - isSearching={Boolean(searchQuery && searchQuery.trim().length > 0)} +
+ + {!isMobile && leftPanelView === 'toolPicker' && ( + + + + + + )}
- ) : ( - // Selected Tool Content View -
- {/* Tool content */} -
- - {selectedToolKey && ( - - )} - + + {searchQuery.trim().length > 0 ? ( +
+ handleToolSelect(id as ToolId)} + searchQuery={searchQuery} + /> +
+ ) : leftPanelView === 'toolPicker' ? ( +
+ handleToolSelect(id as ToolId)} + filteredTools={filteredTools} + isSearching={Boolean(searchQuery && searchQuery.trim().length > 0)} + /> +
+ ) : ( +
+
+ + {selectedToolKey ? ( + + ) : ( +
+ {t('toolPanel.placeholder', 'Choose a tool to get started')} +
+ )} +
+
-
- )} -
+ )} +
+ )} + + {fullscreenExpanded && ( + handleToolSelect(id)} + onToggleDescriptions={() => updatePreference('showLegacyToolDescriptions', !preferences.showLegacyToolDescriptions)} + onExitFullscreenMode={() => setToolPanelMode('sidebar')} + toggleLabel={toggleLabel} + geometry={fullscreenGeometry} + /> + )}
); } diff --git a/frontend/src/components/tools/ToolPanelModePrompt.css b/frontend/src/components/tools/ToolPanelModePrompt.css new file mode 100644 index 0000000000..50e8ad7117 --- /dev/null +++ b/frontend/src/components/tools/ToolPanelModePrompt.css @@ -0,0 +1,200 @@ +.tool-panel-mode-prompt__modal { + background: color-mix(in srgb, var(--bg-toolbar) 94%, transparent); + border: 1px solid color-mix(in srgb, var(--border-subtle) 70%, transparent); + box-shadow: 0 32px 64px color-mix(in srgb, var(--shadow-color, rgba(15, 23, 42, 0.55)) 20%, transparent); + max-width: min(46rem, 100%); +} + +.tool-panel-mode-prompt__options { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 1.5rem; +} + +.tool-panel-mode-prompt__card { + display: flex; + flex-direction: column; + gap: 1rem; + background: linear-gradient(145deg, + color-mix(in srgb, var(--bg-surface) 96%, transparent), + color-mix(in srgb, var(--bg-muted) 70%, transparent) + ); + width: 100%; + max-width: 19rem; +} + +.tool-panel-mode-prompt__card--sidebar { + border: 1px solid color-mix(in srgb, var(--accent-primary, var(--mantine-color-blue-6, #228be6)) 18%, var(--border-subtle)); + background: linear-gradient(165deg, + color-mix(in srgb, var(--bg-surface) 96%, transparent), + color-mix(in srgb, var(--accent-primary, var(--mantine-color-blue-6, #228be6)) 8%, transparent) + ); +} + +.tool-panel-mode-prompt__preview { + border-radius: 0.9rem; + border: 1px solid color-mix(in srgb, var(--border-subtle) 70%, transparent); + background: linear-gradient(135deg, color-mix(in srgb, var(--bg-muted) 82%, transparent), transparent 75%); + padding: 0.75rem; + width: 100%; + display: flex; + gap: 0.65rem; + min-height: 6.5rem; + margin-top: auto; +} + +.tool-panel-mode-prompt__preview--sidebar { + align-items: stretch; +} + +.tool-panel-mode-prompt__sidebar-panel { + width: 3rem; + border-radius: 0.65rem; + padding: 0.45rem; + display: flex; + flex-direction: column; + gap: 0.45rem; + background: linear-gradient(180deg, + color-mix(in srgb, var(--bg-muted) 88%, transparent), + color-mix(in srgb, var(--bg-muted) 72%, transparent) + ); + border: 1px solid color-mix(in srgb, var(--border-subtle) 65%, transparent); +} + +.tool-panel-mode-prompt__sidebar-search { + height: 0.5rem; + border-radius: 0.4rem; + background: color-mix(in srgb, var(--bg-background) 90%, transparent); + border: 1px solid color-mix(in srgb, var(--border-subtle) 60%, transparent); +} + +.tool-panel-mode-prompt__sidebar-item { + height: 0.55rem; + border-radius: 0.35rem; + background: color-mix(in srgb, var(--accent-primary, var(--mantine-color-blue-6, #228be6)) 32%, var(--bg-muted)); +} + +.tool-panel-mode-prompt__sidebar-item--muted { + background: color-mix(in srgb, var(--bg-background) 88%, transparent); +} + +.tool-panel-mode-prompt__workspace { + flex: 1; + border-radius: 0.65rem; + border: 1px solid color-mix(in srgb, var(--border-subtle) 65%, transparent); + padding: 0.5rem; + display: grid; + gap: 0.35rem; + grid-template-rows: 1.4fr 0.6fr; + background: linear-gradient(160deg, + color-mix(in srgb, var(--bg-background) 94%, transparent), + color-mix(in srgb, var(--bg-muted) 68%, transparent) + ); +} + +.tool-panel-mode-prompt__workspace-page { + border-radius: 0.45rem; + background: color-mix(in srgb, var(--bg-surface) 96%, transparent); + border: 1px solid color-mix(in srgb, var(--border-subtle) 55%, transparent); + box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bg-background) 60%, transparent); +} + +.tool-panel-mode-prompt__workspace-page--secondary { + opacity: 0.55; +} + +.tool-panel-mode-prompt__preview--legacy { + align-items: flex-start; + justify-content: center; + padding: 0.65rem 0.6rem; +} + +.tool-panel-mode-prompt__legacy-columns { + width: 100%; + display: flex; + gap: 0.45rem; + justify-content: space-between; +} + +.tool-panel-mode-prompt__legacy-column { + flex: 1; + display: grid; + gap: 0.3rem; +} + +.tool-panel-mode-prompt__legacy-card { + border-radius: 0.45rem; + border: 1px solid color-mix(in srgb, var(--border-subtle) 55%, transparent); + background: linear-gradient(150deg, + color-mix(in srgb, var(--bg-muted) 88%, transparent), + color-mix(in srgb, var(--bg-background) 76%, transparent) + ); + height: 1.2rem; +} + +.tool-panel-mode-prompt__legacy-card--muted { + opacity: 0.55; +} + +.tool-panel-mode-prompt__preview--fullscreen { + align-items: flex-start; + justify-content: center; + padding: 0.65rem 0.6rem; +} + +.tool-panel-mode-prompt__fullscreen-columns { + width: 100%; + display: flex; + gap: 0.45rem; + justify-content: space-between; +} + +.tool-panel-mode-prompt__fullscreen-column { + flex: 1; + display: grid; + gap: 0.3rem; +} + +.tool-panel-mode-prompt__fullscreen-card { + border-radius: 0.45rem; + border: 1px solid color-mix(in srgb, var(--border-subtle) 55%, transparent); + background: linear-gradient(150deg, + color-mix(in srgb, var(--bg-muted) 88%, transparent), + color-mix(in srgb, var(--bg-background) 76%, transparent) + ); + height: 1.2rem; +} + +.tool-panel-mode-prompt__fullscreen-card--muted { + opacity: 0.55; +} + +.tool-panel-mode-prompt__card-content { + display: flex; + flex-direction: column; + gap: 1rem; + flex: 1; +} + +.tool-panel-mode-prompt__action { + box-shadow: none; +} + +.tool-panel-mode-prompt__action:hover { + box-shadow: 0 10px 18px color-mix(in srgb, var(--accent-primary, var(--mantine-color-blue-6, #228be6)) 25%, transparent); +} + +.tool-panel-mode-prompt__maybe-later { + color: color-mix(in srgb, var(--text-secondary) 90%, var(--text-muted)); +} + +.tool-panel-mode-prompt__maybe-later:hover { + background: color-mix(in srgb, var(--bg-muted) 78%, transparent); +} + +@media (max-width: 600px) { + .tool-panel-mode-prompt__options { + grid-template-columns: 1fr; + justify-items: stretch; + } +} diff --git a/frontend/src/components/tools/ToolPanelModePrompt.tsx b/frontend/src/components/tools/ToolPanelModePrompt.tsx new file mode 100644 index 0000000000..770cf2eee0 --- /dev/null +++ b/frontend/src/components/tools/ToolPanelModePrompt.tsx @@ -0,0 +1,141 @@ +import { useEffect, useState } from 'react'; +import { Badge, Button, Card, Group, Modal, Stack, Text } from '@mantine/core'; +import { useTranslation } from 'react-i18next'; +import { useToolWorkflow } from '../../contexts/ToolWorkflowContext'; +import { usePreferences } from '../../contexts/PreferencesContext'; +import './ToolPanelModePrompt.css'; +import type { ToolPanelMode } from '../../constants/toolPanel'; + +const ToolPanelModePrompt = () => { + const { t } = useTranslation(); + const { toolPanelMode, setToolPanelMode } = useToolWorkflow(); + const { preferences, updatePreference } = usePreferences(); + const [opened, setOpened] = useState(false); + + const shouldShowPrompt = !preferences.toolPanelModePromptSeen; + + useEffect(() => { + if (shouldShowPrompt) { + setOpened(true); + } + }, [shouldShowPrompt]); + + const handleSelect = (mode: ToolPanelMode) => { + setToolPanelMode(mode); + updatePreference('defaultToolPanelMode', mode); + updatePreference('toolPanelModePromptSeen', true); + setOpened(false); + }; + + const handleDismiss = () => { + updatePreference('toolPanelModePromptSeen', true); + setOpened(false); + }; + + return ( + + + + {t('toolPanel.modePrompt.description', 'Preview both layouts and decide how you want to explore Stirling PDF tools.')} + +
+ + + + + {t('toolPanel.modePrompt.sidebarTitle', 'Sidebar mode')} + + {t('toolPanel.modePrompt.sidebarDescription', 'Keep tools alongside your workspace for quick switching.')} + + + + {t('toolPanel.modePrompt.recommended', 'Recommended')} + + +
+
+ + + + + +
+
+
+
+
+
+ + + + + + + {t('toolPanel.modePrompt.fullscreenTitle', 'Fullscreen mode')} + + {t('toolPanel.modePrompt.fullscreenDescription', 'Browse every tool in a catalogue that covers the workspace until you pick one.')} + + +
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+ +
+
+
+ + + + ); +}; + +export default ToolPanelModePrompt; diff --git a/frontend/src/components/tools/ToolPicker.tsx b/frontend/src/components/tools/ToolPicker.tsx index 4b0c545364..f158f036e0 100644 --- a/frontend/src/components/tools/ToolPicker.tsx +++ b/frontend/src/components/tools/ToolPicker.tsx @@ -4,13 +4,20 @@ import { useTranslation } from "react-i18next"; import { ToolRegistryEntry } from "../../data/toolsTaxonomy"; import "./toolPicker/ToolPicker.css"; import { useToolSections } from "../../hooks/useToolSections"; +import type { SubcategoryGroup } from "../../hooks/useToolSections"; +import { useFavoriteToolItems } from "../../hooks/tools/useFavoriteToolItems"; import NoToolsFound from "./shared/NoToolsFound"; import { renderToolButtons } from "./shared/renderToolButtons"; +import Badge from "../shared/Badge"; +import SubcategoryHeader from "./shared/SubcategoryHeader"; +import ToolButton from "./toolPicker/ToolButton"; +import { useToolWorkflow } from "../../contexts/ToolWorkflowContext"; +import { ToolId } from "../../types/toolId"; interface ToolPickerProps { selectedToolKey: string | null; onSelect: (id: string) => void; - filteredTools: Array<{ item: [string, ToolRegistryEntry]; matchedText?: string }>; + filteredTools: Array<{ item: [ToolId, ToolRegistryEntry]; matchedText?: string }>; isSearching?: boolean; } @@ -61,11 +68,23 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa }, []); const { sections: visibleSections } = useToolSections(filteredTools); + const { favoriteTools, toolRegistry } = useToolWorkflow(); + + const favoriteToolItems = useFavoriteToolItems(favoriteTools, toolRegistry); const quickSection = useMemo( () => visibleSections.find(s => s.key === 'quick'), [visibleSections] ); + + const recommendedItems = useMemo(() => { + if (!quickSection) return [] as Array<{ id: string; tool: ToolRegistryEntry }>; + const items: Array<{ id: string; tool: ToolRegistryEntry }> = []; + quickSection.subcategories.forEach((sc: SubcategoryGroup) => sc.tools.forEach((toolEntry) => items.push(toolEntry))); + return items; + }, [quickSection]); + + const recommendedCount = useMemo(() => favoriteToolItems.length + recommendedItems.length, [favoriteToolItems.length, recommendedItems.length]); const allSection = useMemo( () => visibleSections.find(s => s.key === 'all'), [visibleSections] @@ -87,7 +106,9 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa }; // Build flat list by subcategory for search mode - const { searchGroups } = useToolSections(isSearching ? filteredTools : []); + const emptyFilteredTools: ToolPickerProps['filteredTools'] = []; + const effectiveFilteredForSearch: ToolPickerProps['filteredTools'] = isSearching ? filteredTools : emptyFilteredTools; + const { searchGroups } = useToolSections(effectiveFilteredForSearch); return ( ) : ( - searchGroups.map(group => renderToolButtons(t, group, selectedToolKey, onSelect)) + searchGroups.map(group => renderToolButtons(t, group, selectedToolKey, onSelect, true, false, filteredTools, true)) )} ) : ( @@ -142,24 +163,46 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa onClick={() => scrollTo(quickAccessRef)} > {t("toolPicker.quickAccess", "QUICK ACCESS")} - - {quickSection?.subcategories.reduce((acc, sc) => acc + sc.tools.length, 0)} - + + {recommendedCount} +
- {quickSection?.subcategories.map(sc => - renderToolButtons(t, sc, selectedToolKey, onSelect, false) + {favoriteToolItems.length > 0 && ( + + +
+ {favoriteToolItems.map(({ id, tool }) => ( + + ))} +
+
+ )} + {recommendedItems.length > 0 && ( + + +
+ {recommendedItems.map(({ id, tool }) => ( + + ))} +
+
)}
@@ -188,25 +231,16 @@ const ToolPicker = ({ selectedToolKey, onSelect, filteredTools, isSearching = fa onClick={() => scrollTo(allToolsRef)} > {t("toolPicker.allTools", "ALL TOOLS")} - + {allSection?.subcategories.reduce((acc, sc) => acc + sc.tools.length, 0)} - +
- {allSection?.subcategories.map(sc => - renderToolButtons(t, sc, selectedToolKey, onSelect, true) - )} + {allSection?.subcategories.map((sc: SubcategoryGroup) => + renderToolButtons(t, sc, selectedToolKey, onSelect, true, false, undefined, true) + )} diff --git a/frontend/src/components/tools/ToolRenderer.tsx b/frontend/src/components/tools/ToolRenderer.tsx index fbd8100d1f..89d1b025ca 100644 --- a/frontend/src/components/tools/ToolRenderer.tsx +++ b/frontend/src/components/tools/ToolRenderer.tsx @@ -1,10 +1,11 @@ import { Suspense } from "react"; import { useToolWorkflow } from "../../contexts/ToolWorkflowContext"; import { BaseToolProps } from "../../types/tool"; +import { ToolId } from "../../types/toolId"; import ToolLoadingFallback from "./ToolLoadingFallback"; interface ToolRendererProps extends BaseToolProps { - selectedToolKey: string; + selectedToolKey: ToolId; } @@ -16,7 +17,14 @@ const ToolRenderer = ({ }: ToolRendererProps) => { // Get the tool from context (instead of direct hook call) const { toolRegistry } = useToolWorkflow(); - const selectedTool = toolRegistry[selectedToolKey]; + const selectedTool = (selectedToolKey in toolRegistry) + ? toolRegistry[selectedToolKey as ToolId] + : undefined; + + // Handle tools that only work in workbenches (read, multiTool) + if (selectedTool && !selectedTool.component && selectedTool.workbench) { + return null; // These tools render in their workbench, not in the sidebar + } if (!selectedTool || !selectedTool.component) { return
Tool not found: {selectedToolKey}
; diff --git a/frontend/src/components/tools/automate/AutomationCreation.tsx b/frontend/src/components/tools/automate/AutomationCreation.tsx index b5e793ccb3..9dee457539 100644 --- a/frontend/src/components/tools/automate/AutomationCreation.tsx +++ b/frontend/src/components/tools/automate/AutomationCreation.tsx @@ -10,6 +10,7 @@ import { Divider, Modal } from '@mantine/core'; +import { Z_INDEX_AUTOMATE_MODAL } from '../../../styles/zIndex'; import CheckIcon from '@mui/icons-material/Check'; import { ToolRegistry } from '../../../data/toolsTaxonomy'; import ToolConfigurationModal from './ToolConfigurationModal'; @@ -221,6 +222,7 @@ export default function AutomationCreation({ mode, existingAutomation, onBack, o onClose={handleCancelBack} title={t('automate.creation.unsavedChanges.title', 'Unsaved Changes')} centered + zIndex={Z_INDEX_AUTOMATE_MODAL} > diff --git a/frontend/src/components/tools/automate/AutomationEntry.tsx b/frontend/src/components/tools/automate/AutomationEntry.tsx index 8f739bcbfe..57f480f1ff 100644 --- a/frontend/src/components/tools/automate/AutomationEntry.tsx +++ b/frontend/src/components/tools/automate/AutomationEntry.tsx @@ -8,6 +8,7 @@ import ContentCopyIcon from '@mui/icons-material/ContentCopy'; import { Tooltip } from '../../shared/Tooltip'; import { ToolIcon } from '../../shared/ToolIcon'; import { ToolRegistryEntry } from '../../../data/toolsTaxonomy'; +import { ToolId } from 'src/types/toolId'; interface AutomationEntryProps { /** Optional title for the automation (usually for custom ones) */ @@ -31,7 +32,7 @@ interface AutomationEntryProps { /** Copy handler (for suggested automations) */ onCopy?: () => void; /** Tool registry to resolve operation names */ - toolRegistry?: Record; + toolRegistry?: Record; } export default function AutomationEntry({ @@ -55,8 +56,8 @@ export default function AutomationEntry({ // Helper function to resolve tool display names const getToolDisplayName = (operation: string): string => { - if (toolRegistry?.[operation]?.name) { - return toolRegistry[operation].name; + if (toolRegistry?.[operation as ToolId]?.name) { + return toolRegistry[operation as ToolId].name; } // Fallback to translation or operation key return t(`${operation}.title`, operation); diff --git a/frontend/src/components/tools/automate/AutomationRun.tsx b/frontend/src/components/tools/automate/AutomationRun.tsx index 2d6bda6fc6..d25008a3a5 100644 --- a/frontend/src/components/tools/automate/AutomationRun.tsx +++ b/frontend/src/components/tools/automate/AutomationRun.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { Button, Text, Stack, Group, Card, Progress } from "@mantine/core"; import PlayArrowIcon from "@mui/icons-material/PlayArrow"; @@ -30,7 +30,7 @@ export default function AutomationRun({ automation, onComplete, automateOperatio const hasResults = automateOperation?.files.length > 0 || automateOperation?.downloadUrl !== null; // Initialize execution steps from automation - React.useEffect(() => { + useEffect(() => { if (automation?.operations) { const steps = automation.operations.map((op: any, index: number) => { const tool = toolRegistry[op.operation as keyof typeof toolRegistry]; @@ -47,7 +47,7 @@ export default function AutomationRun({ automation, onComplete, automateOperatio }, [automation, toolRegistry]); // Cleanup when component unmounts - React.useEffect(() => { + useEffect(() => { return () => { // Reset progress state when component unmounts setExecutionSteps([]); diff --git a/frontend/src/components/tools/automate/AutomationSelection.tsx b/frontend/src/components/tools/automate/AutomationSelection.tsx index a10ebb1393..edbec569ac 100644 --- a/frontend/src/components/tools/automate/AutomationSelection.tsx +++ b/frontend/src/components/tools/automate/AutomationSelection.tsx @@ -7,6 +7,7 @@ import { useSuggestedAutomations } from "../../../hooks/tools/automate/useSugges import { AutomationConfig, SuggestedAutomation } from "../../../types/automation"; import { iconMap } from './iconMap'; import { ToolRegistryEntry } from '../../../data/toolsTaxonomy'; +import { ToolId } from '../../../types/toolId'; interface AutomationSelectionProps { savedAutomations: AutomationConfig[]; @@ -15,7 +16,7 @@ interface AutomationSelectionProps { onEdit: (automation: AutomationConfig) => void; onDelete: (automation: AutomationConfig) => void; onCopyFromSuggested: (automation: SuggestedAutomation) => void; - toolRegistry: Record; + toolRegistry: Record; } export default function AutomationSelection({ diff --git a/frontend/src/components/tools/automate/ToolConfigurationModal.tsx b/frontend/src/components/tools/automate/ToolConfigurationModal.tsx index fca5892185..271fd91a14 100644 --- a/frontend/src/components/tools/automate/ToolConfigurationModal.tsx +++ b/frontend/src/components/tools/automate/ToolConfigurationModal.tsx @@ -9,6 +9,7 @@ import { Text, Alert } from '@mantine/core'; +import { Z_INDEX_AUTOMATE_MODAL } from '../../../styles/zIndex'; import SettingsIcon from '@mui/icons-material/Settings'; import CheckIcon from '@mui/icons-material/Check'; import CloseIcon from '@mui/icons-material/Close'; @@ -103,6 +104,7 @@ export default function ToolConfigurationModal({ opened, tool, onSave, onCancel, } size="lg" centered + zIndex={Z_INDEX_AUTOMATE_MODAL} > diff --git a/frontend/src/components/tools/automate/ToolList.tsx b/frontend/src/components/tools/automate/ToolList.tsx index 3cb2b7a7eb..bde0625f8d 100644 --- a/frontend/src/components/tools/automate/ToolList.tsx +++ b/frontend/src/components/tools/automate/ToolList.tsx @@ -6,12 +6,13 @@ import CloseIcon from "@mui/icons-material/Close"; import AddCircleOutline from "@mui/icons-material/AddCircleOutline"; import { AutomationTool } from "../../../types/automation"; import { ToolRegistryEntry } from "../../../data/toolsTaxonomy"; +import { ToolId } from "../../../types/toolId"; import ToolSelector from "./ToolSelector"; import AutomationEntry from "./AutomationEntry"; interface ToolListProps { tools: AutomationTool[]; - toolRegistry: Record; + toolRegistry: Record; onToolUpdate: (index: number, updates: Partial) => void; onToolRemove: (index: number) => void; onToolConfigure: (index: number) => void; @@ -34,7 +35,7 @@ export default function ToolList({ const handleToolSelect = (index: number, newOperation: string) => { const defaultParams = getToolDefaultParameters(newOperation); - const toolEntry = toolRegistry[newOperation]; + const toolEntry = toolRegistry[newOperation as ToolId]; // If tool has no settingsComponent, it's automatically configured const isConfigured = !toolEntry?.automationSettings; diff --git a/frontend/src/components/tools/automate/ToolSelector.tsx b/frontend/src/components/tools/automate/ToolSelector.tsx index 5cbc4b3ab4..bbf8e95f8f 100644 --- a/frontend/src/components/tools/automate/ToolSelector.tsx +++ b/frontend/src/components/tools/automate/ToolSelector.tsx @@ -6,11 +6,12 @@ import { useToolSections } from '../../../hooks/useToolSections'; import { renderToolButtons } from '../shared/renderToolButtons'; import ToolSearch from '../toolPicker/ToolSearch'; import ToolButton from '../toolPicker/ToolButton'; +import { ToolId } from '../../../types/toolId'; interface ToolSelectorProps { onSelect: (toolKey: string) => void; excludeTools?: string[]; - toolRegistry: Record; // Pass registry as prop to break circular dependency + toolRegistry: Record; // Pass registry as prop to break circular dependency selectedValue?: string; // For showing current selection when editing existing tool placeholder?: string; // Custom placeholder text } @@ -30,7 +31,7 @@ export default function ToolSelector({ // Filter out excluded tools (like 'automate' itself) and tools that don't support automation const baseFilteredTools = useMemo(() => { - return Object.entries(toolRegistry).filter(([key, tool]) => + return (Object.entries(toolRegistry) as [ToolId, ToolRegistryEntry][]).filter(([key, tool]) => !excludeTools.includes(key) && getToolSupportsAutomate(tool) ); }, [toolRegistry, excludeTools]); @@ -53,20 +54,20 @@ export default function ToolSelector({ // Create filtered tool registry for ToolSearch const filteredToolRegistry = useMemo(() => { - const registry: Record = {}; + const registry: Record = {} as Record; baseFilteredTools.forEach(([key, tool]) => { - registry[key] = tool; + registry[key as ToolId] = tool; }); return registry; }, [baseFilteredTools]); // Transform filteredTools to the expected format for useToolSections const transformedFilteredTools = useMemo(() => { - return filteredTools.map(([id, tool]) => ({ item: [id, tool] as [string, ToolRegistryEntry] })); + return filteredTools.map(([id, tool]) => ({ item: [id as ToolId, tool] as [ToolId, ToolRegistryEntry] })); }, [filteredTools]); // Use the same tool sections logic as the main ToolPicker - const { sections, searchGroups } = useToolSections(transformedFilteredTools); + const { sections, searchGroups } = useToolSections(transformedFilteredTools as any /* FIX ME */); // Determine what to display: search results or organized sections const isSearching = searchTerm.trim().length > 0; @@ -88,7 +89,7 @@ export default function ToolSelector({ } // Find the "all" section which contains all tools without duplicates - const allSection = sections.find(s => (s as any).key === 'all'); + const allSection = sections.find(s => s.key === 'all'); return allSection?.subcategories || []; }, [isSearching, searchGroups, sections, baseFilteredTools]); @@ -100,7 +101,7 @@ export default function ToolSelector({ const renderedTools = useMemo(() => displayGroups.map((subcategory) => - renderToolButtons(t, subcategory, null, handleToolSelect, !isSearching, true) + renderToolButtons(t, subcategory as any, null, handleToolSelect, !isSearching, true) ), [displayGroups, handleToolSelect, isSearching, t] ); @@ -142,8 +143,8 @@ export default function ToolSelector({ // Get display value for selected tool const getDisplayValue = () => { - if (selectedValue && toolRegistry[selectedValue]) { - return toolRegistry[selectedValue].name; + if (selectedValue && toolRegistry[selectedValue as ToolId]) { + return toolRegistry[selectedValue as ToolId].name; } return placeholder || t('automate.creation.tools.add', 'Add a tool...'); }; @@ -152,11 +153,11 @@ export default function ToolSelector({
{/* Always show the target - either selected tool or search input */} - {selectedValue && toolRegistry[selectedValue] && !opened ? ( + {selectedValue && toolRegistry[selectedValue as ToolId] && !opened ? ( // Show selected tool in AutomationEntry style when tool is selected and dropdown closed
- {}} rounded={true} disableNavigation={true}>
) : ( diff --git a/frontend/src/components/tools/fullscreen/CompactToolItem.tsx b/frontend/src/components/tools/fullscreen/CompactToolItem.tsx new file mode 100644 index 0000000000..896d704395 --- /dev/null +++ b/frontend/src/components/tools/fullscreen/CompactToolItem.tsx @@ -0,0 +1,108 @@ +import React from 'react'; +import { Text } from '@mantine/core'; +import { useTranslation } from 'react-i18next'; +import { Tooltip } from '../../shared/Tooltip'; +import HotkeyDisplay from '../../hotkeys/HotkeyDisplay'; +import FavoriteStar from '../toolPicker/FavoriteStar'; +import { ToolRegistryEntry, getSubcategoryColor } from '../../../data/toolsTaxonomy'; +import { getIconBackground, getIconStyle, getItemClasses, useToolMeta } from './shared'; + +interface CompactToolItemProps { + id: string; + tool: ToolRegistryEntry; + isSelected: boolean; + onClick: () => void; + tooltipPortalTarget?: HTMLElement | undefined; +} + +const CompactToolItem: React.FC = ({ id, tool, isSelected, onClick, tooltipPortalTarget }) => { + const { t } = useTranslation(); + const { binding, isFav, toggleFavorite, disabled } = useToolMeta(id, tool); + const categoryColor = getSubcategoryColor(tool.subcategoryId); + const iconBg = getIconBackground(categoryColor, false); + const iconClasses = 'tool-panel__fullscreen-list-icon'; + + let iconNode: React.ReactNode = null; + if (React.isValidElement<{ style?: React.CSSProperties }>(tool.icon)) { + const element = tool.icon as React.ReactElement<{ style?: React.CSSProperties }>; + iconNode = React.cloneElement(element, { + style: { + ...(element.props.style || {}), + fontSize: '1.5rem', + }, + }); + } else { + iconNode = tool.icon; + } + + const compactButton = ( + + ); + + const tooltipContent = disabled + ? ( + {t('toolPanel.fullscreen.comingSoon', 'Coming soon:')} {tool.description} + ) + : ( +
+ {tool.description} + {binding && ( +
+ + {t('settings.hotkeys.shortcut', 'Shortcut')} + + +
+ )} +
+ ); + + return ( + + {compactButton} + + ); +}; + +export default CompactToolItem; + + diff --git a/frontend/src/components/tools/fullscreen/DetailedToolItem.tsx b/frontend/src/components/tools/fullscreen/DetailedToolItem.tsx new file mode 100644 index 0000000000..c9b518b77d --- /dev/null +++ b/frontend/src/components/tools/fullscreen/DetailedToolItem.tsx @@ -0,0 +1,88 @@ +import React from 'react'; +import { Text } from '@mantine/core'; +import { useTranslation } from 'react-i18next'; +import HotkeyDisplay from '../../hotkeys/HotkeyDisplay'; +import FavoriteStar from '../toolPicker/FavoriteStar'; +import { ToolRegistryEntry, getSubcategoryColor } from '../../../data/toolsTaxonomy'; +import { getIconBackground, getIconStyle, getItemClasses, useToolMeta } from './shared'; + +interface DetailedToolItemProps { + id: string; + tool: ToolRegistryEntry; + isSelected: boolean; + onClick: () => void; +} + +const DetailedToolItem: React.FC = ({ id, tool, isSelected, onClick }) => { + const { t } = useTranslation(); + const { binding, isFav, toggleFavorite, disabled } = useToolMeta(id, tool); + + const categoryColor = getSubcategoryColor(tool.subcategoryId); + const iconBg = getIconBackground(categoryColor, true); + const iconClasses = 'tool-panel__fullscreen-icon'; + + let iconNode: React.ReactNode = null; + if (React.isValidElement<{ style?: React.CSSProperties }>(tool.icon)) { + const element = tool.icon as React.ReactElement<{ style?: React.CSSProperties }>; + iconNode = React.cloneElement(element, { + style: { + ...(element.props.style || {}), + fontSize: '1.75rem', + }, + }); + } else { + iconNode = tool.icon; + } + + return ( + + ); +}; + +export default DetailedToolItem; + + diff --git a/frontend/src/components/tools/fullscreen/shared.ts b/frontend/src/components/tools/fullscreen/shared.ts new file mode 100644 index 0000000000..6355c3828b --- /dev/null +++ b/frontend/src/components/tools/fullscreen/shared.ts @@ -0,0 +1,45 @@ +import { useHotkeys } from '../../../contexts/HotkeyContext'; +import { useToolWorkflow } from '../../../contexts/ToolWorkflowContext'; +import { ToolRegistryEntry } from '../../../data/toolsTaxonomy'; +import { ToolId } from '../../../types/toolId'; + +export const getItemClasses = (isDetailed: boolean): string => { + return isDetailed ? 'tool-panel__fullscreen-item--detailed' : ''; +}; + +export const getIconBackground = (categoryColor: string, isDetailed: boolean): string => { + const baseColor = isDetailed ? 'var(--fullscreen-bg-icon-detailed)' : 'var(--fullscreen-bg-icon-compact)'; + const blend1 = isDetailed ? '18%' : '15%'; + const blend2 = isDetailed ? '8%' : '6%'; + + return `linear-gradient(135deg, + color-mix(in srgb, ${categoryColor} ${blend1}, ${baseColor}), + color-mix(in srgb, ${categoryColor} ${blend2}, ${baseColor}) + )`; +}; + +export const getIconStyle = (): Record => { + return {}; +}; + +export const isToolDisabled = (id: string, tool: ToolRegistryEntry): boolean => { + return !tool.component && !tool.link && id !== 'read' && id !== 'multiTool'; +}; + +export function useToolMeta(id: string, tool: ToolRegistryEntry) { + const { hotkeys } = useHotkeys(); + const { isFavorite, toggleFavorite } = useToolWorkflow(); + + const isFav = isFavorite(id as ToolId); + const binding = hotkeys[id as ToolId]; + const disabled = isToolDisabled(id, tool); + + return { + binding, + isFav, + toggleFavorite: () => toggleFavorite(id as ToolId), + disabled, + }; +} + + diff --git a/frontend/src/components/tools/overlayPdfs/OverlayPdfsSettings.module.css b/frontend/src/components/tools/overlayPdfs/OverlayPdfsSettings.module.css new file mode 100644 index 0000000000..b6efb3bbc0 --- /dev/null +++ b/frontend/src/components/tools/overlayPdfs/OverlayPdfsSettings.module.css @@ -0,0 +1,50 @@ +.fileListContainer { + max-height: 16.25rem; + overflow-y: auto; + overflow-x: hidden; + width: 100%; +} + +.fileItem { + border: 1px solid var(--mantine-color-gray-3); + border-radius: var(--mantine-radius-sm); + align-items: center; + width: 100%; +} + +.fileNameContainer { + flex: 1; + min-width: 0; +} + +.fileName { + font-size: var(--mantine-font-size-sm); + font-weight: 400; + line-height: 1.2; + display: -webkit-box; + -webkit-line-clamp: 1; + line-clamp: 1; + -webkit-box-orient: vertical; + overflow: hidden; + white-space: normal; + word-break: break-word; +} + +.fileSize { + flex-shrink: 0; +} + +.removeButton { + flex-shrink: 0; +} + +.countLabel { + width: 140px; + flex-shrink: 0; +} + +.fileGroup { + flex: 1; + min-width: 0; + align-items: center; +} diff --git a/frontend/src/components/tools/overlayPdfs/OverlayPdfsSettings.tsx b/frontend/src/components/tools/overlayPdfs/OverlayPdfsSettings.tsx new file mode 100644 index 0000000000..43ba64db5a --- /dev/null +++ b/frontend/src/components/tools/overlayPdfs/OverlayPdfsSettings.tsx @@ -0,0 +1,178 @@ +import { Stack, Text, Group, Select, SegmentedControl, NumberInput, Button, ActionIcon, Divider } from '@mantine/core'; +import { useTranslation } from 'react-i18next'; +import { type OverlayPdfsParameters, type OverlayMode } from '../../../hooks/tools/overlayPdfs/useOverlayPdfsParameters'; +import LocalIcon from '../../shared/LocalIcon'; +import { useFilesModalContext } from '../../../contexts/FilesModalContext'; +import styles from './OverlayPdfsSettings.module.css'; + +interface OverlayPdfsSettingsProps { + parameters: OverlayPdfsParameters; + onParameterChange: (key: K, value: OverlayPdfsParameters[K]) => void; + disabled?: boolean; +} + +export default function OverlayPdfsSettings({ parameters, onParameterChange, disabled = false }: OverlayPdfsSettingsProps) { + const { t } = useTranslation(); + const { openFilesModal } = useFilesModalContext(); + + const handleOverlayFilesChange = (files: File[]) => { + onParameterChange('overlayFiles', files); + // Reset counts to match number of files if in FixedRepeatOverlay + if (parameters.overlayMode === 'FixedRepeatOverlay') { + const nextCounts = files.map((_, i) => parameters.counts[i] && parameters.counts[i] > 0 ? parameters.counts[i] : 1); + onParameterChange('counts', nextCounts); + } + }; + + const handleModeChange = (mode: OverlayMode) => { + onParameterChange('overlayMode', mode); + if (mode !== 'FixedRepeatOverlay') { + onParameterChange('counts', []); + } else if (parameters.overlayFiles?.length > 0) { + onParameterChange('counts', parameters.overlayFiles.map((_, i) => parameters.counts[i] && parameters.counts[i] > 0 ? parameters.counts[i] : 1)); + } + }; + + const handleOpenOverlayFilesModal = () => { + if (disabled) return; + openFilesModal({ + customHandler: (files: File[]) => { + handleOverlayFilesChange([...(parameters.overlayFiles || []), ...files]); + } + }); + }; + + return ( + + + + {t('overlay-pdfs.mode.label', 'Overlay Mode')} +