diff --git a/docs/native-vs-web.md b/docs/native-vs-web.md index 6488fec28..2d8008f83 100644 --- a/docs/native-vs-web.md +++ b/docs/native-vs-web.md @@ -76,7 +76,7 @@ shared: native renders the same modals too, so the desktop is effectively single-window now. A modal's backdrop dims and blocks clicks but does not dismiss; the ✕ button closes it. -### Parallelism — web: single-threaded +### Parallelism — web: main thread plus one parse/serialize worker `wasm32` has no threads without SharedArrayBuffer (which needs COOP/COEP headers GitHub Pages can't set). `crate::par::prelude` re-exports `rayon` on native and sequential `std` iterators on wasm, so the same call sites run in diff --git a/locales/ar-SA/opencadstudio.ftl b/locales/ar-SA/opencadstudio.ftl index 779d90e06..fdbed4be6 100644 --- a/locales/ar-SA/opencadstudio.ftl +++ b/locales/ar-SA/opencadstudio.ftl @@ -2407,6 +2407,7 @@ command = .tcount-placement-overwrite-prefix-suffix-overw = TCOUNT الموضع [استبدال/بادئة/لاحقة] <استبدال>: file = + .already-loaded-different-contents-kept = "__ocs_fmt_0__" محمّل بالفعل بمحتوى مختلف؛ تم الاحتفاظ بالملف الموجود. .parse-ms-purge-ms-caches-ms-xref-ms-total-ms = التحليل __ocs_fmt_0__ ملث · التنظيف __ocs_fmt_1__ ملث · الذاكرة المؤقتة __ocs_fmt_2__ ملث · المرجع الخارجي __ocs_fmt_3__ ملث · الإجمالي __ocs_fmt_4__ ملث .archive-save-the-drawing-first-it-has-no-file = ARCHIVE: احفظ الرسم أولًا (ليس له مسار ملف بعد). .autosaved-1-drawing = حُفظ رسم واحد تلقائيًا diff --git a/locales/bg-BG/opencadstudio.ftl b/locales/bg-BG/opencadstudio.ftl index 6607b3076..13fb68fdd 100644 --- a/locales/bg-BG/opencadstudio.ftl +++ b/locales/bg-BG/opencadstudio.ftl @@ -2394,6 +2394,7 @@ command = .tcount-placement-overwrite-prefix-suffix-overw = TCOUNT поставяне [Замяна/Префикс/Суфикс] <Замяна>: file = + .already-loaded-different-contents-kept = "__ocs_fmt_0__" вече е зареден с различно съдържание; съществуващият файл е запазен. .parse-ms-purge-ms-caches-ms-xref-ms-total-ms = анализ __ocs_fmt_0__ms · изчистване __ocs_fmt_1__ms · кешове __ocs_fmt_2__ms · xref __ocs_fmt_3__ms · общо __ocs_fmt_4__ms .archive-save-the-drawing-first-it-has-no-file = ARCHIVE: първо запази чертежа (все още няма път до файл). .autosaved-1-drawing = Автоматично запазен 1 чертеж diff --git a/locales/cs-CZ/opencadstudio.ftl b/locales/cs-CZ/opencadstudio.ftl index f7dd005b9..d552f8c72 100644 --- a/locales/cs-CZ/opencadstudio.ftl +++ b/locales/cs-CZ/opencadstudio.ftl @@ -2394,6 +2394,7 @@ command = .tcount-placement-overwrite-prefix-suffix-overw = Umístění TCOUNT [přepsat/předpona/přípona] : file = + .already-loaded-different-contents-kept = "__ocs_fmt_0__" je již načten s jiným obsahem; existující soubor byl zachován. .parse-ms-purge-ms-caches-ms-xref-ms-total-ms = analyzovat __ocs_fmt_0__ms · vyčistit __ocs_fmt_1__ms · mezipaměti __ocs_fmt_2__ms · xref __ocs_fmt_3__ms · celkem __ocs_fmt_4__ms .archive-save-the-drawing-first-it-has-no-file = ARCHIVE: nejprve uložte výkres (ještě nemá žádnou cestu k souboru). .autosaved-1-drawing = Automaticky uložen 1 výkres diff --git a/locales/de-DE/opencadstudio.ftl b/locales/de-DE/opencadstudio.ftl index c6295a1e4..2fe0e55c5 100644 --- a/locales/de-DE/opencadstudio.ftl +++ b/locales/de-DE/opencadstudio.ftl @@ -2395,6 +2395,7 @@ command = .tcount-placement-overwrite-prefix-suffix-overw = TCOUNT Platzierung [Überschreiben/Präfix/Suffix] <Überschreiben>: file = + .already-loaded-different-contents-kept = "__ocs_fmt_0__" ist bereits mit anderem Inhalt geladen; die vorhandene Datei wurde behalten. .parse-ms-purge-ms-caches-ms-xref-ms-total-ms = analysiere __ocs_fmt_0__ms · Säubere __ocs_fmt_1__ms · Puffer __ocs_fmt_2__ms · xref __ocs_fmt_3__ms · gesamt __ocs_fmt_4__ms .archive-save-the-drawing-first-it-has-no-file = ARCHIVE: Speichern Sie die Zeichnung zuerst (sie hat noch keinen Dateipfad). .autosaved-1-drawing = 1 Zeichnung automatisch gespeichert diff --git a/locales/el-GR/opencadstudio.ftl b/locales/el-GR/opencadstudio.ftl index 04f01124e..524f78f73 100644 --- a/locales/el-GR/opencadstudio.ftl +++ b/locales/el-GR/opencadstudio.ftl @@ -2394,6 +2394,7 @@ command = .tcount-placement-overwrite-prefix-suffix-overw = TCOUNT τοποθέτηση [Αντικατάσταση/Πρόθεμα/Κατάληξη] <Αντικατάσταση>: file = + .already-loaded-different-contents-kept = Το "__ocs_fmt_0__" είναι ήδη φορτωμένο με διαφορετικό περιεχόμενο· διατηρήθηκε το υπάρχον αρχείο. .parse-ms-purge-ms-caches-ms-xref-ms-total-ms = ανάλυση __ocs_fmt_0__ms · εκκαθάριση __ocs_fmt_1__ms · κρυφές μνήμες __ocs_fmt_2__ms · xref __ocs_fmt_3__ms · σύνολο __ocs_fmt_4__ms .archive-save-the-drawing-first-it-has-no-file = ARCHIVE: αποθηκεύστε πρώτα το σχέδιο (δεν έχει ακόμη διαδρομή αρχείου). .autosaved-1-drawing = Αυτόματη αποθήκευση 1 σχεδίου diff --git a/locales/en-US/opencadstudio.ftl b/locales/en-US/opencadstudio.ftl index c08809772..4083d9d32 100644 --- a/locales/en-US/opencadstudio.ftl +++ b/locales/en-US/opencadstudio.ftl @@ -2395,6 +2395,7 @@ command = .tcount-placement-overwrite-prefix-suffix-overw = TCOUNT placement [Overwrite/Prefix/Suffix] : file = + .already-loaded-different-contents-kept = "__ocs_fmt_0__" is already loaded with different contents; the existing file was kept. .parse-ms-purge-ms-caches-ms-xref-ms-total-ms = parse __ocs_fmt_0__ms · purge __ocs_fmt_1__ms · caches __ocs_fmt_2__ms · xref __ocs_fmt_3__ms · total __ocs_fmt_4__ms .archive-save-the-drawing-first-it-has-no-file = ARCHIVE: save the drawing first (it has no file path yet). .autosaved-1-drawing = Autosaved 1 drawing diff --git a/locales/es-ES/opencadstudio.ftl b/locales/es-ES/opencadstudio.ftl index a8bfbb96f..4bb8be323 100644 --- a/locales/es-ES/opencadstudio.ftl +++ b/locales/es-ES/opencadstudio.ftl @@ -2395,6 +2395,7 @@ command = .tcount-placement-overwrite-prefix-suffix-overw = TCOUNT colocación [Sobrescribir/Prefijo/Sufijo] : file = + .already-loaded-different-contents-kept = "__ocs_fmt_0__" ya está cargado con contenido diferente; se conservó el archivo existente. .parse-ms-purge-ms-caches-ms-xref-ms-total-ms = análisis __ocs_fmt_0__ ms · limpieza __ocs_fmt_1__ ms · cachés __ocs_fmt_2__ ms · ref. ext. __ocs_fmt_3__ ms · total __ocs_fmt_4__ ms .archive-save-the-drawing-first-it-has-no-file = ARCHIVE: guarde primero el dibujo (todavía no tiene una ruta de archivo). .autosaved-1-drawing = Se guardó automáticamente 1 dibujo diff --git a/locales/fi-FI/opencadstudio.ftl b/locales/fi-FI/opencadstudio.ftl index db0f70558..be6186fb9 100644 --- a/locales/fi-FI/opencadstudio.ftl +++ b/locales/fi-FI/opencadstudio.ftl @@ -2394,6 +2394,7 @@ command = .tcount-placement-overwrite-prefix-suffix-overw = TCOUNT-sijoittelu [Korvaa/etuliite/liite] : file = + .already-loaded-different-contents-kept = "__ocs_fmt_0__" on jo ladattu eri sisällöllä; olemassa oleva tiedosto säilytettiin. .parse-ms-purge-ms-caches-ms-xref-ms-total-ms = jäsentää __ocs_fmt_0__ms · tyhjentää __ocs_fmt_1__ms · välimuistit __ocs_fmt_2__ms · xref __ocs_fmt_3__ms · yhteensä __ocs_fmt_4__ms .archive-save-the-drawing-first-it-has-no-file = ARCHIVE: tallenna piirustus ensin (sillä ei ole vielä tiedostopolkua). .autosaved-1-drawing = Automaattisesti tallennettu 1 piirros diff --git a/locales/fr-FR/opencadstudio.ftl b/locales/fr-FR/opencadstudio.ftl index 8b0862500..f27958a5e 100644 --- a/locales/fr-FR/opencadstudio.ftl +++ b/locales/fr-FR/opencadstudio.ftl @@ -2395,6 +2395,7 @@ command = .tcount-placement-overwrite-prefix-suffix-overw = TCOUNT Position [Remplacer/Préfixe/Suffixe] : file = + .already-loaded-different-contents-kept = "__ocs_fmt_0__" est déjà chargé avec un contenu différent ; le fichier existant a été conservé. .parse-ms-purge-ms-caches-ms-xref-ms-total-ms = Analyse : __ocs_fmt_0__ ms, purge : __ocs_fmt_1__ ms, caches : __ocs_fmt_2__ ms, références externes : __ocs_fmt_3__ ms, total : __ocs_fmt_4__ ms .archive-save-the-drawing-first-it-has-no-file = ARCHIVE : enregistrer le dessin en premier (il n'a pas encore de chemin de fichier). .autosaved-1-drawing = 1 dessin enregistré automatiquement diff --git a/locales/hi-IN/opencadstudio.ftl b/locales/hi-IN/opencadstudio.ftl index d4955b773..56ebffba0 100644 --- a/locales/hi-IN/opencadstudio.ftl +++ b/locales/hi-IN/opencadstudio.ftl @@ -2383,6 +2383,7 @@ command = .tcount-placement-overwrite-prefix-suffix-overw = TCOUNT स्थान [अधिलेखित/उपसर्ग/प्रत्यय] <अधिलेखित>: file = + .already-loaded-different-contents-kept = "__ocs_fmt_0__" अलग सामग्री के साथ पहले से लोड है; मौजूदा फ़ाइल रखी गई। .parse-ms-purge-ms-caches-ms-xref-ms-total-ms = पार्स __ocs_fmt_0__ms · purge __ocs_fmt_1__Ms · कैश __ocs_fmt_2__ms · xref __ocs_fmt_3__ms · कुल __ocs_fmt_4__ms .archive-save-the-drawing-first-it-has-no-file = ARCHIVE: पहले ड्रॉइंग सहेजें (अभी फ़ाइल पथ निर्धारित नहीं है)। .autosaved-1-drawing = 1 ड्रॉइंग स्वतः सहेजी गई diff --git a/locales/hu-HU/opencadstudio.ftl b/locales/hu-HU/opencadstudio.ftl index d3b7ed03d..6c750bc68 100644 --- a/locales/hu-HU/opencadstudio.ftl +++ b/locales/hu-HU/opencadstudio.ftl @@ -2407,6 +2407,7 @@ command = .tcount-placement-overwrite-prefix-suffix-overw = TCOUNT elhelyezés [Overwrite (Felülírás) / Prefix (Előtag) / Suffix (Utótag)] : file = + .already-loaded-different-contents-kept = A(z) "__ocs_fmt_0__" már betöltve eltérő tartalommal; a meglévő fájl megőrizve. .parse-ms-purge-ms-caches-ms-xref-ms-total-ms = elemzése __ocs_fmt_0__ms · __ocs_fmt_1__ms törlése · gyorsítótárak __ocs_fmt_2__ms · xref __ocs_fmt_3__ms · összesen __ocs_fmt_4__ms .archive-save-the-drawing-first-it-has-no-file = ARCHIVE: először mentse a rajzot (még nincs fájl elérési útja). .autosaved-1-drawing = Automatikusan mentett 1 rajzot diff --git a/locales/it-IT/opencadstudio.ftl b/locales/it-IT/opencadstudio.ftl index c9c84b7db..6434fcbab 100644 --- a/locales/it-IT/opencadstudio.ftl +++ b/locales/it-IT/opencadstudio.ftl @@ -2394,6 +2394,7 @@ command = .tcount-placement-overwrite-prefix-suffix-overw = Posizionamento TCOUNT [Sovrascrivi/Prefisso/Suffisso] : file = + .already-loaded-different-contents-kept = "__ocs_fmt_0__" è già caricato con contenuti diversi; il file esistente è stato mantenuto. .parse-ms-purge-ms-caches-ms-xref-ms-total-ms = analizza __ocs_fmt_0__ms · elimina __ocs_fmt_1__ms · memorizza nella cache __ocs_fmt_2__ms · xref __ocs_fmt_3__ms · totale __ocs_fmt_4__ms .archive-save-the-drawing-first-it-has-no-file = ARCHIVE: salva prima il disegno (non ha ancora il percorso del file). .autosaved-1-drawing = 1 disegno salvato automaticamente diff --git a/locales/ja-JP/opencadstudio.ftl b/locales/ja-JP/opencadstudio.ftl index 50d5001e5..9c6c28de4 100644 --- a/locales/ja-JP/opencadstudio.ftl +++ b/locales/ja-JP/opencadstudio.ftl @@ -2385,6 +2385,7 @@ command = .tcount-placement-overwrite-prefix-suffix-overw = TCOUNT 配置 [Overwrite/Prefix/Suffix] : file = + .already-loaded-different-contents-kept = "__ocs_fmt_0__" は既に別の内容で読み込まれています。既存のファイルを保持しました。 .parse-ms-purge-ms-caches-ms-xref-ms-total-ms = 解析 __ocs_fmt_0__ms · パージ __ocs_fmt_1__ms · キャッシュ __ocs_fmt_2__ms · 外部参照 __ocs_fmt_3__ms · 合計 __ocs_fmt_4__ms .archive-save-the-drawing-first-it-has-no-file = ARCHIVE: 先に図面を保存してください(まだファイルパスがありません)。 .autosaved-1-drawing = 1 件の図面を自動保存しました diff --git a/locales/ko-KR/opencadstudio.ftl b/locales/ko-KR/opencadstudio.ftl index b833be814..4347c755f 100644 --- a/locales/ko-KR/opencadstudio.ftl +++ b/locales/ko-KR/opencadstudio.ftl @@ -2394,6 +2394,7 @@ command = .tcount-placement-overwrite-prefix-suffix-overw = TCOUNT 배치 [덮어쓰기/접두사/접미사] : file = + .already-loaded-different-contents-kept = "__ocs_fmt_0__"이(가) 이미 다른 내용으로 로드되었습니다. 기존 파일을 유지했습니다. .parse-ms-purge-ms-caches-ms-xref-ms-total-ms = __ocs_fmt_0__ms 구문 분석 · __ocs_fmt_1__ms 제거 · 캐시 __ocs_fmt_2__ms · xref __ocs_fmt_3__ms · 총 __ocs_fmt_4__ms .archive-save-the-drawing-first-it-has-no-file = ARCHIVE: 먼저 도면을 저장합니다(아직 파일 경로가 없음). .autosaved-1-drawing = 그림 1개 자동 저장됨 diff --git a/locales/nl-NL/opencadstudio.ftl b/locales/nl-NL/opencadstudio.ftl index ea97bfd56..c3fba1d6a 100644 --- a/locales/nl-NL/opencadstudio.ftl +++ b/locales/nl-NL/opencadstudio.ftl @@ -2395,6 +2395,7 @@ command = .tcount-placement-overwrite-prefix-suffix-overw = TCOUNT Plaatsing [Overschrijven/Voorvoegsel/Achtervoegsel] : file = + .already-loaded-different-contents-kept = "__ocs_fmt_0__" is al geladen met andere inhoud; het bestaande bestand is bewaard. .parse-ms-purge-ms-caches-ms-xref-ms-total-ms = ontleed __ocs_fmt_0__ms · zuivering __ocs_fmt_1__ms · caches __ocs_fmt_2__ms · xref __ocs_fmt_3__ms · totaal __ocs_fmt_4__ms .archive-save-the-drawing-first-it-has-no-file = ARCHIVE: sla de tekening eerst op (er is nog geen bestandspad). .autosaved-1-drawing = Automatisch opslaan van 1 tekening diff --git a/locales/pl-PL/opencadstudio.ftl b/locales/pl-PL/opencadstudio.ftl index 083b0fca1..71867c3ee 100644 --- a/locales/pl-PL/opencadstudio.ftl +++ b/locales/pl-PL/opencadstudio.ftl @@ -2394,6 +2394,7 @@ command = .tcount-placement-overwrite-prefix-suffix-overw = Umieszczenie TCOUNT [Nadpisanie/Przedrostek/Sufiks] : file = + .already-loaded-different-contents-kept = "__ocs_fmt_0__" jest już wczytany z inną zawartością; zachowano istniejący plik. .parse-ms-purge-ms-caches-ms-xref-ms-total-ms = analizuj __ocs_fmt_0__ms · usuwaj __ocs_fmt_1__ms · pamięci podręczne __ocs_fmt_2__ms · xref __ocs_fmt_3__ms · łącznie __ocs_fmt_4__ms .archive-save-the-drawing-first-it-has-no-file = ARCHIVE: najpierw zapisz rysunek (nie ma on jeszcze ścieżki do pliku). .autosaved-1-drawing = Automatycznie zapisano 1 rysunek diff --git a/locales/pt-BR/opencadstudio.ftl b/locales/pt-BR/opencadstudio.ftl index 31112ac0f..d197ef06e 100644 --- a/locales/pt-BR/opencadstudio.ftl +++ b/locales/pt-BR/opencadstudio.ftl @@ -2395,6 +2395,7 @@ command = .tcount-placement-overwrite-prefix-suffix-overw = TCOUNT posicionamento [Sobrescrever/Prefixo/Sufixo] : file = + .already-loaded-different-contents-kept = "__ocs_fmt_0__" já está carregado com conteúdo diferente; o ficheiro existente foi mantido. .parse-ms-purge-ms-caches-ms-xref-ms-total-ms = análise __ocs_fmt_0__ms · limpeza __ocs_fmt_1__ms · caches __ocs_fmt_2__ms · ref. externa __ocs_fmt_3__ms · total __ocs_fmt_4__ms .archive-save-the-drawing-first-it-has-no-file = ARCHIVE: salve o desenho primeiro (ele ainda não tem um caminho de arquivo). .autosaved-1-drawing = 1 desenho salvo automaticamente diff --git a/locales/ru-RU/opencadstudio.ftl b/locales/ru-RU/opencadstudio.ftl index 2c3ef193c..783c40ffb 100644 --- a/locales/ru-RU/opencadstudio.ftl +++ b/locales/ru-RU/opencadstudio.ftl @@ -2398,6 +2398,7 @@ command = .tcount-placement-overwrite-prefix-suffix-overw = TCOUNT Размещение [Заменить/Префикс/Суффикс] <Заменить>: file = + .already-loaded-different-contents-kept = "__ocs_fmt_0__" уже загружен с другим содержимым; существующий файл сохранён. .parse-ms-purge-ms-caches-ms-xref-ms-total-ms = parse __ocs_fmt_0__ms · чистка __ocs_fmt_1__ms · кэши __ocs_fmt_2__ms · xref __ocs_fmt_3__ms · общая __ocs_fmt_4__ms .archive-save-the-drawing-first-it-has-no-file = ARCHIVE: сначала сохраните чертёж (путь к файлу ещё не задан). .autosaved-1-drawing = Автоматически сохранён 1 чертёж diff --git a/locales/tr-TR/opencadstudio.ftl b/locales/tr-TR/opencadstudio.ftl index 75f5c2017..75eef21cf 100644 --- a/locales/tr-TR/opencadstudio.ftl +++ b/locales/tr-TR/opencadstudio.ftl @@ -2379,6 +2379,7 @@ command = .tcount-placement-overwrite-prefix-suffix-overw = TCOUNT Yerleştirme [Üzerine yaz/Önek/Sonek] <Üzerine yaz>: file = + .already-loaded-different-contents-kept = "__ocs_fmt_0__" zaten farklı içerikle yüklü; mevcut dosya tutuldu. .parse-ms-purge-ms-caches-ms-xref-ms-total-ms = ayrıştırma __ocs_fmt_0__ ms · temizleme __ocs_fmt_1__ ms · önbellekler __ocs_fmt_2__ ms · xref __ocs_fmt_3__ ms · toplam __ocs_fmt_4__ ms .archive-save-the-drawing-first-it-has-no-file = ARCHIVE: önce çizimi kaydedin; çizimin henüz bir dosya yolu yok. .autosaved-1-drawing = 1 çizim otomatik kaydedildi diff --git a/locales/zh-CN/opencadstudio.ftl b/locales/zh-CN/opencadstudio.ftl index 470eed825..8e8eb546e 100644 --- a/locales/zh-CN/opencadstudio.ftl +++ b/locales/zh-CN/opencadstudio.ftl @@ -2382,6 +2382,7 @@ command = .tcount-placement-overwrite-prefix-suffix-overw = TCOUNT 放置方式 [覆盖/前缀/后缀] <覆盖>: file = + .already-loaded-different-contents-kept = "__ocs_fmt_0__"已以不同内容加载;保留了现有文件。 .parse-ms-purge-ms-caches-ms-xref-ms-total-ms = 解析 __ocs_fmt_0__ 毫秒 · 清除 __ocs_fmt_1__ 毫秒 · 缓存 __ocs_fmt_2__ 毫秒 · xref __ocs_fmt_3__ 毫秒 · 总计 __ocs_fmt_4__ 毫秒 .archive-save-the-drawing-first-it-has-no-file = ARCHIVE:请先保存图形(尚未设置文件路径)。 .autosaved-1-drawing = 自动保存了 1 个绘图 diff --git a/locales/zh-TW/opencadstudio.ftl b/locales/zh-TW/opencadstudio.ftl index f4284e328..7e94d5923 100644 --- a/locales/zh-TW/opencadstudio.ftl +++ b/locales/zh-TW/opencadstudio.ftl @@ -2394,6 +2394,7 @@ command = .tcount-placement-overwrite-prefix-suffix-overw = TCOUNT 放置方式 [覆蓋/前綴/後綴] <覆蓋>: file = + .already-loaded-different-contents-kept = "__ocs_fmt_0__"已以不同內容載入;保留了現有檔案。 .parse-ms-purge-ms-caches-ms-xref-ms-total-ms = 解析 __ocs_fmt_0__ 毫秒 · 清除 __ocs_fmt_1__ 毫秒 · 快取 __ocs_fmt_2__ 毫秒 · xref __ocs_fmt_3__ 毫秒 · 總計 __ocs_fmt_4__ 毫秒 .archive-save-the-drawing-first-it-has-no-file = ARCHIVE:請先儲存圖形(尚未設定檔案路徑)。 .autosaved-1-drawing = 自動儲存了 1 個繪圖 diff --git a/src/app/annotation_data.rs b/src/app/annotation_data.rs index 24c268706..ea74fbd56 100644 --- a/src/app/annotation_data.rs +++ b/src/app/annotation_data.rs @@ -1338,7 +1338,7 @@ impl OpenCADStudio { if let Some(name) = take_last_collision().take() { self.command_line.push_warning( crate::tf!( - "\"{name}\" is already loaded from another folder with different contents; re-picking it was ignored to keep the existing link." + "\"{name}\" is already loaded with different contents; the existing file was kept." ) .as_ref(), ); @@ -1911,6 +1911,16 @@ impl OpenCADStudio { .pick_files() .await .unwrap_or_default(); + // Each picked file becomes a full parsed document held in + // the registry for the session; a bound keeps a giant + // multi-pick from exhausting the wasm heap (tab death). + const MAX_EXTRACTION_SOURCES: usize = 16; + if handles.len() > MAX_EXTRACTION_SOURCES { + return Err(crate::tf!( + "At most {MAX_EXTRACTION_SOURCES} drawings can be added in the browser." + ) + .into_owned()); + } let mut paths = Vec::new(); for handle in handles { let name = handle.file_name(); diff --git a/src/app/update/file.rs b/src/app/update/file.rs index 9b8eb5f70..3813603c3 100644 --- a/src/app/update/file.rs +++ b/src/app/update/file.rs @@ -2850,6 +2850,13 @@ impl OpenCADStudio { let rest = self.continue_tab_close_queue(); return Task::batch([continuation, rest]); } + } else if matches!(self.pending_close, Some(crate::app::PendingClose::Quit)) { + // Mirror the native Quit continuation: offer to save the + // next dirty tab, or finish the quit. + if self.tabs.iter().any(|tab| tab.dirty) { + return self.open_unsaved_dialog_window(); + } + return Task::batch([self.close_unsaved_dialog_window(), self.exit_app()]); } else if self.pending_close.is_some() { return self.open_unsaved_dialog_window(); } diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index a14f69bcc..b9630f41b 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -861,6 +861,16 @@ impl OpenCADStudio { .file_name() .map(|name| name.to_string_lossy().into_owned()) .unwrap_or_else(|| path.to_string_lossy().into_owned()); + // The start page stays clickable under the open overlay, + // so a recent can race an in-flight open and clobber it + // (its WebFileOpened then fails the id check and the + // bytes are lost) — refuse like on_open_file does. + if self.opening.is_some() { + self.command_line.push_info( + crate::t!("An open is already in progress.").as_ref(), + ); + return Task::none(); + } let state = std::sync::Arc::new(crate::io::OpenProgressState::new( crate::app::OPEN_PHASE_READING, )); diff --git a/src/io/web_drop.rs b/src/io/web_drop.rs index 5fa8402c5..1f6ac571b 100644 --- a/src/io/web_drop.rs +++ b/src/io/web_drop.rs @@ -290,3 +290,20 @@ fn is_image_name(name: &str) -> bool { "png" | "jpg" | "jpeg" | "bmp" | "tiff" | "tif" ) } + +#[cfg(test)] +mod tests { + use super::is_drawing_name; + + #[test] + fn drawing_names_use_the_worker_convention() { + assert!(is_drawing_name("plan.dwg")); + assert!(is_drawing_name("PLAN.DXF")); + assert!(is_drawing_name("DWG")); + assert!(is_drawing_name("dxf")); + assert!(!is_drawing_name("plan.png")); + assert!(!is_drawing_name("dwgx")); + assert!(!is_drawing_name("plan.dwg.txt")); + assert!(!is_drawing_name("")); + } +} diff --git a/src/io/web_recent.rs b/src/io/web_recent.rs index ffa93bef1..c3aaa64bb 100644 --- a/src/io/web_recent.rs +++ b/src/io/web_recent.rs @@ -312,3 +312,23 @@ pub(crate) async fn modified_ms(name: &str) -> Option { .ok()?; Some(file.last_modified()) } + +#[cfg(test)] +mod tests { + use super::name_hash; + + /// Canonical FNV-1a 64 over the raw name bytes — the reference vector + /// from the spec's test suite. + #[test] + fn name_hash_is_fnv1a64() { + assert_eq!(name_hash(""), "cbf29ce484222325"); + assert_eq!(name_hash("a"), "af63dc4c8601ec8c"); + assert_eq!(name_hash("foobar"), "85944171f73967e8"); + } + + #[test] + fn name_hash_distinguishes_case_and_extension() { + assert_ne!(name_hash("a.dwg"), name_hash("A.dwg")); + assert_ne!(name_hash("a.dwg"), name_hash("a.dxf")); + } +} diff --git a/src/io/xref.rs b/src/io/xref.rs index ef3f28742..c010674d7 100644 --- a/src/io/xref.rs +++ b/src/io/xref.rs @@ -3529,6 +3529,16 @@ fn relative_point_cloud_path(doc: &mut CadDocument, handle: Handle, host: &Path) mod web_registry_tests { use super::*; + /// The registries are process-global and the default harness runs tests + /// in parallel; the eviction test's inserts can evict a sibling test's + /// entry mid-assert. Registry tests hold this lock for their duration. + fn registry_guard() -> std::sync::MutexGuard<'static, ()> { + static LOCK: OnceLock> = OnceLock::new(); + LOCK.get_or_init(|| Mutex::new(())) + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()) + } + fn outcome() -> codec::ReadOutcome { codec::ReadOutcome::new(CadDocument::new(), codec::ReadStats::default()) } @@ -3560,6 +3570,7 @@ mod web_registry_tests { #[test] fn registry_finds_a_registered_source_by_trailing_name() { + let _registry = registry_guard(); let registered = "Registry-Find-Site.dwg"; register_source(registered, Arc::new(vec![1, 2, 3])); // The drawing's stored path is the author's absolute foreign path; @@ -3579,6 +3590,7 @@ mod web_registry_tests { /// re-registration of a live name neither evicts nor duplicates it. #[test] fn source_registry_evicts_oldest_beyond_the_cap() { + let _registry = registry_guard(); for i in 0..(SOURCE_CAP + 2) { register_source(&format!("f{i}.dwg"), Arc::new(vec![i as u8])); } @@ -3618,6 +3630,7 @@ mod web_registry_tests { #[test] fn register_source_supersedes_a_cached_parse_of_the_same_file() { + let _registry = registry_guard(); let name = "Registry-Supersede-Plan.dwg"; register_parsed(name, outcome()); // Re-registering the same file under a differently-cased path name diff --git a/src/locale_catalog.rs b/src/locale_catalog.rs index f7aff6b1a..51368a785 100644 --- a/src/locale_catalog.rs +++ b/src/locale_catalog.rs @@ -2278,6 +2278,7 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "Opening \"{name}\" ({size_label})…" => Some(("common", "opening-name-size-label")), "Opening OCS Discussions for help and questions..." => Some(("view", "opening-ocs-discussions-for-help-and-questions")), "Opening OCS Web..." => Some(("view", "opening-ocs-web")), + "\"{name}\" is already loaded with different contents; the existing file was kept." => Some(("file", "already-loaded-different-contents-kept")), "Opening Patreon page..." => Some(("view", "opening-patreon-page")), "Opening feedback page..." => Some(("view", "opening-feedback-page")), "Opening file" => Some(("ui", "opening-file")),