Preferences: Data Recovery's Folder gets a Choose button

File Handling > Data Recovery > Folder has a Choose... button on the
desktop that opens the folder picker and puts the chosen folder in the
field. Cancel leaves the field as it was, and the field stays editable.
A chosen folder that no file can be created in is refused with a status
message. The web build has no folder picker, so the button is not shown
there.
This commit is contained in:
dotCooCoo
2026-11-05 03:04:39 +07:00
committed by bflatastic
parent 3e96d59fd8
commit 9613bfb31d
24 changed files with 156 additions and 2 deletions
+1
View File
@@ -5054,6 +5054,7 @@ digits Malayalam Malaiàlam
@msg Create Outlines: select text objects Crea contorns: seleccioneu objectes de text
@msg damaged malmès
@msg Data Recovery has nowhere to keep copies: set Preferences → File Handling → Data Recovery → Folder (recoveryFolder) La recuperació de dades no té cap lloc on guardar còpies: configureu Preferències → Gestió de fitxers → Recuperació de dades → Carpeta (recoveryFolder)
@msg Data Recovery can't keep copies in {path}: {e} La recuperació de dades no pot guardar còpies a {path}: {e}
@msg data the program reads is damaged or cut short, or the program asks for a position in a file that has none les dades que llegeix el programa estan malmeses o tallades, o el programa demana una posició en un fitxer que no en té
@msg decimals must be {_1}–{_2} (got {_3}) els decimals han de ser {_1}–{_2} (rebut {_3})
@msg Dimension point is outside the canvas El punt de dimensió és fora del llenç
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -4291,6 +4291,7 @@ imageTrace Outline Obrys
@msg Create from Guides: the guides make more than {MAX_PIECES} slices Vytvořit z vodítek: vodítka vytvářejí více než {MAX_PIECES} řezů
@msg Create from Selection: the selection has no area Vytvořit z výběru: výběr nemá žádnou plochu
@msg Data Recovery has nowhere to keep copies: set Preferences → File Handling → Data Recovery → Folder (recoveryFolder) Obnovení dat nemá kam ukládat kopie: nastavte Předvolby → Práce se soubory → Obnovení dat → Složka (recoveryFolder)
@msg Data Recovery can't keep copies in {path}: {e} Obnovení dat nemůže ukládat kopie do {path}: {e}
@msg direct-select anchor points to cut at (not the end points of open paths) nástrojem Přímý výběr vyberte kotevní body, kde se má cesta rozdělit (ne koncové body otevřených cest)
@msg Divide Objects Below: select a path Rozdělit objekty pod: vyberte cestu
@msg Divide Objects Below: select exactly one path Rozdělit objekty pod: vyberte právě jednu cestu
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3898,6 +3898,7 @@ theme Appearance Erscheinungsbild
@msg Create from Guides: the guides make more than {MAX_PIECES} slices Aus Hilfslinien erstellen: Die Hilfslinien ergeben mehr als {MAX_PIECES} Slices
@msg Create from Selection: the selection has no area Aus Auswahl erstellen: Die Auswahl hat keine Fläche
@msg Data Recovery has nowhere to keep copies: set Preferences → File Handling → Data Recovery → Folder (recoveryFolder) Die Datenwiederherstellung hat keinen Ort für ihre Kopien: Legen Sie Voreinstellungen → Dateihandhabung → Datenwiederherstellung → Ordner (recoveryFolder) fest
@msg Data Recovery can't keep copies in {path}: {e} Die Datenwiederherstellung kann keine Kopien in {path} ablegen: {e}
@msg direct-select anchor points to cut at (not the end points of open paths) wählen Sie mit der Direktauswahl die Ankerpunkte aus, an denen geschnitten werden soll (nicht die Endpunkte offener Pfade)
@msg Divide Objects Below: select a path Darunterliegende Objekte aufteilen: Wählen Sie einen Pfad aus
@msg Divide Objects Below: select exactly one path Darunterliegende Objekte aufteilen: Wählen Sie genau einen Pfad aus
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -2183,6 +2183,7 @@ axis Both Και τα δύο
@msg Create from Guides: the guides make more than {MAX_PIECES} slices Δημιουργία από οδηγούς: οι οδηγοί σχηματίζουν περισσότερες από {MAX_PIECES} τομές
@msg Create from Selection: the selection has no area Δημιουργία από επιλογή: η επιλογή δεν έχει επιφάνεια
@msg Data Recovery has nowhere to keep copies: set Preferences → File Handling → Data Recovery → Folder (recoveryFolder) Η ανάκτηση δεδομένων δεν διαθέτει θέση αποθήκευσης αντιγράφων: ορίστε Προτιμήσεις → Χειρισμός αρχείων → Ανάκτηση δεδομένων → Φάκελος (recoveryFolder)
@msg Data Recovery can't keep copies in {path}: {e} Η ανάκτηση δεδομένων δεν μπορεί να αποθηκεύσει αντίγραφα στο {path}: {e}
@msg direct-select anchor points to cut at (not the end points of open paths) Επιλέξτε άμεσα τα σημεία αγκύρωσης όπου θα γίνει η αποκοπή (όχι τα τελικά σημεία ανοιχτών διαδρομών)
@msg Divide Objects Below: select a path Διαίρεση αντικειμένων από κάτω: επιλέξτε μια διαδρομή
@msg Divide Objects Below: select exactly one path Διαίρεση αντικειμένων από κάτω: επιλέξτε ακριβώς μία διαδρομή
Can't render this file because it contains an unexpected character in line 163 and column 5.
+1
View File
@@ -3983,6 +3983,7 @@ axis Both Ambos
@msg Create from Guides: the guides make more than {MAX_PIECES} slices Crear a partir de guías: las guías forman más de {MAX_PIECES} sectores
@msg Create from Selection: the selection has no area Crear a partir de la selección: la selección no tiene área
@msg Data Recovery has nowhere to keep copies: set Preferences → File Handling → Data Recovery → Folder (recoveryFolder) Recuperación de datos no tiene dónde guardar copias: configura Preferencias → Manejo de archivos → Recuperación de datos → Carpeta (recoveryFolder)
@msg Data Recovery can't keep copies in {path}: {e} Recuperación de datos no puede guardar copias en {path}: {e}
@msg direct-select anchor points to cut at (not the end points of open paths) selecciona con Selección directa los puntos de ancla en los que cortar (no los puntos finales de trazados abiertos)
@msg Divide Objects Below: select a path Dividir objetos inferiores: selecciona un trazado
@msg Divide Objects Below: select exactly one path Dividir objetos inferiores: selecciona exactamente un trazado
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -4205,6 +4205,7 @@ imageTrace Outline Ääriviiva
@msg Create from Guides: the guides make more than {MAX_PIECES} slices Luo apuviivoista: apuviivat muodostavat yli {MAX_PIECES} viipaletta
@msg Create from Selection: the selection has no area Luo valinnasta: valinnalla ei ole pinta-alaa
@msg Data Recovery has nowhere to keep copies: set Preferences → File Handling → Data Recovery → Folder (recoveryFolder) Tietojen palautuksella ei ole paikkaa kopioille: määritä Asetukset → Tiedostojen käsittely → Tietojen palautus → Kansio (recoveryFolder)
@msg Data Recovery can't keep copies in {path}: {e} Tietojen palautus ei voi tallentaa kopioita kansioon {path}: {e}
@msg direct-select anchor points to cut at (not the end points of open paths) valitse suoravalinnalla ankkuripisteet, joista leikataan (ei avoimien polkujen päätepisteitä)
@msg Divide Objects Below: select a path Jaa alla olevat objektit: valitse polku
@msg Divide Objects Below: select exactly one path Jaa alla olevat objektit: valitse tasan yksi polku
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3973,6 +3973,7 @@ axis Both Les deux
@msg Create from Guides: the guides make more than {MAX_PIECES} slices Créer d’après les repères : les repères forment plus de {MAX_PIECES} tranches
@msg Create from Selection: the selection has no area Créer d’après la sélection : la sélection n’a pas de surface
@msg Data Recovery has nowhere to keep copies: set Preferences → File Handling → Data Recovery → Folder (recoveryFolder) La récupération des données n’a aucun emplacement où conserver les copies : définissez Préférences → Gestion des fichiers → Récupération des données → Dossier (recoveryFolder)
@msg Data Recovery can't keep copies in {path}: {e} La récupération des données ne peut pas conserver de copies dans {path} ({e})
@msg direct-select anchor points to cut at (not the end points of open paths) sélectionnez avec l’outil Sélection directe les points d’ancrage où couper (pas les extrémités des tracés ouverts)
@msg Divide Objects Below: select a path Diviser les objets inférieurs : sélectionnez un tracé
@msg Divide Objects Below: select exactly one path Diviser les objets inférieurs : sélectionnez exactement un tracé
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -4774,6 +4774,7 @@ imageTrace Outline Körvonal
@msg Create Outlines: select text objects Átalakítás útvonallá: jelölj ki szövegobjektumokat
@msg damaged sérült
@msg Data Recovery has nowhere to keep copies: set Preferences → File Handling → Data Recovery → Folder (recoveryFolder) Az adat-helyreállításnak nincs hova mentenie a másolatokat: állítsd be a Beállítások → Fájlkezelés → Adat-helyreállítás → Mappa értéket (recoveryFolder)
@msg Data Recovery can't keep copies in {path}: {e} Az adat-helyreállítás nem tud másolatokat menteni ide: {path}: {e}
@msg decimals must be {_1}–{_2} (got {_3}) a tizedesjegyek száma {_1}–{_2} lehet (kapott érték: {_3})
@msg direct-select anchor points to cut at (not the end points of open paths) jelöld ki közvetlen kijelöléssel a vágási horgonypontokat (nyitott útvonal végpontja nem lehet)
@msgplural Discarded {n} recovered document|Discarded {n} recovered documents Elvetett helyreállított dokumentumok: {n}
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -4025,6 +4025,7 @@ axis Both Entrambi
@msg Create from Guides: the guides make more than {MAX_PIECES} slices Crea da guide: le guide generano più di {MAX_PIECES} sezioni
@msg Create from Selection: the selection has no area Crea da selezione: la selezione non ha area
@msg Data Recovery has nowhere to keep copies: set Preferences → File Handling → Data Recovery → Folder (recoveryFolder) Il recupero dati non ha dove conservare le copie: imposta Preferenze → Gestione file → Recupero dati → Cartella (recoveryFolder)
@msg Data Recovery can't keep copies in {path}: {e} Il recupero dati non può conservare copie in {path}: {e}
@msg direct-select anchor points to cut at (not the end points of open paths) seleziona con Selezione diretta i punti di ancoraggio in cui tagliare (non i punti finali dei tracciati aperti)
@msg Divide Objects Below: select a path Dividi oggetti sottostanti: seleziona un tracciato
@msg Divide Objects Below: select exactly one path Dividi oggetti sottostanti: seleziona esattamente un tracciato
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -4320,6 +4320,7 @@ swatch Bright Violet ビビッドバイオレット
@msg `{key}` must be one of {_1} `{key}` は {_1} のいずれかにしてください
@msg unknown preference `{key}` `{key}` は不明な環境設定の項目です
@msg Data Recovery has nowhere to keep copies: set Preferences → File Handling → Data Recovery → Folder (recoveryFolder) データ復帰のコピーを保存する場所がありません: 環境設定 → ファイル管理 → データ復帰 → フォルダー (recoveryFolder) を設定してください
@msg Data Recovery can't keep copies in {path}: {e} データ復帰は {path} にコピーを保存できません: {e}
@msg `{area}` is no recovery area name `{area}` は復帰用領域の名前として使えません
@msg `{name}` is no recovery entry name `{name}` は復帰用エントリの名前として使えません
@msg no clock to keep a heartbeat by ハートビートの記録に必要な時刻を取得できません
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3770,6 +3770,7 @@ font-technology Variable ცვლადი
@msg Create from Guides: the guides make more than {MAX_PIECES} slices დამხმარე ხაზებიდან შექმნა: დამხმარე ხაზები {MAX_PIECES}-ზე მეტ ნაჭერს ქმნის
@msg Create from Selection: the selection has no area მონიშნულიდან შექმნა: მონიშნულს ფართობი არ აქვს
@msg Data Recovery has nowhere to keep copies: set Preferences → File Handling → Data Recovery → Folder (recoveryFolder) მონაცემთა აღდგენას ასლების შესანახი ადგილი არ აქვს: მიუთითეთ პარამეტრები → ფაილების დამუშავება → მონაცემთა აღდგენა → საქაღალდე (recoveryFolder)
@msg Data Recovery can't keep copies in {path}: {e} მონაცემთა აღდგენა ვერ ინახავს ასლებს აქ: {path}: {e}
@msg direct-select anchor points to cut at (not the end points of open paths) პირდაპირი მონიშვნით მონიშნეთ საყრდენი წერტილები, სადაც გაიჭრება (არა ღია ტრაექტორიების ბოლო წერტილები)
@msg Divide Objects Below: select a path ქვედა ობიექტების დაყოფა: მონიშნეთ ტრაექტორია
@msg Divide Objects Below: select exactly one path ქვედა ობიექტების დაყოფა: მონიშნეთ ზუსტად ერთი ტრაექტორია
Can't render this file because it is too large.
+1
View File
@@ -4247,6 +4247,7 @@ theme Appearance 외관
@msg `{key}` must be one of {_1} `{key}`은(는) {_1} 중 하나여야 합니다
@msg unknown preference `{key}` 알 수 없는 환경 설정 `{key}`
@msg Data Recovery has nowhere to keep copies: set Preferences → File Handling → Data Recovery → Folder (recoveryFolder) 데이터 복구에 복사본을 보관할 곳이 없습니다. 환경 설정 → 파일 처리 → 데이터 복구 → 폴더(recoveryFolder)를 지정하세요
@msg Data Recovery can't keep copies in {path}: {e} 데이터 복구가 {path}에 복사본을 보관할 수 없습니다: {e}
@msg `{area}` is no recovery area name `{area}`은(는) 복구 영역 이름이 아닙니다
@msg `{name}` is no recovery entry name `{name}`은(는) 복구 항목 이름이 아닙니다
@msg no clock to keep a heartbeat by 하트비트를 유지할 시계가 없습니다
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -4114,6 +4114,7 @@ imageTrace Outline Kontur
@msg Create from Guides: the guides make more than {MAX_PIECES} slices Utwórz z linii pomocniczych: linie pomocnicze tworzą więcej niż {MAX_PIECES} plasterków
@msg Create from Selection: the selection has no area Utwórz z zaznaczenia: zaznaczenie nie ma powierzchni
@msg Data Recovery has nowhere to keep copies: set Preferences → File Handling → Data Recovery → Folder (recoveryFolder) Odzyskiwanie danych nie ma gdzie przechowywać kopii: ustaw Preferencje → Obsługa plików → Odzyskiwanie danych → Folder (recoveryFolder)
@msg Data Recovery can't keep copies in {path}: {e} Odzyskiwanie danych nie może przechowywać kopii w {path}: {e}
@msg direct-select anchor points to cut at (not the end points of open paths) zaznacz bezpośrednio punkty kontrolne, w których ma nastąpić cięcie (nie punkty końcowe otwartych ścieżek)
@msg Divide Objects Below: select a path Podziel obiekty poniżej: zaznacz ścieżkę
@msg Divide Objects Below: select exactly one path Podziel obiekty poniżej: zaznacz dokładnie jedną ścieżkę
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3572,6 +3572,7 @@ axis Both Ambos
@msg Create from Guides: the guides make more than {MAX_PIECES} slices Criar a partir de guias: as guias formam mais de {MAX_PIECES} fatias
@msg Create from Selection: the selection has no area Criar a partir da seleção: a seleção não tem área
@msg Data Recovery has nowhere to keep copies: set Preferences → File Handling → Data Recovery → Folder (recoveryFolder) A recuperação de dados não tem onde guardar cópias: defina Preferências → Manipulação de arquivos → Recuperação de dados → Pasta (recoveryFolder)
@msg Data Recovery can't keep copies in {path}: {e} A recuperação de dados não pode guardar cópias em {path}: {e}
@msg direct-select anchor points to cut at (not the end points of open paths) selecione diretamente os pontos de ancoragem onde cortar (não as extremidades de traçados abertos)
@msg Divide Objects Below: select a path Dividir objetos abaixo: selecione um traçado
@msg Divide Objects Below: select exactly one path Dividir objetos abaixo: selecione exatamente um traçado
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -4020,6 +4020,7 @@ axis Both Оба
@msg Create from Guides: the guides make more than {MAX_PIECES} slices Создание из направляющих: направляющие образуют более {MAX_PIECES} фрагментов
@msg Create from Selection: the selection has no area Создание из выделения: у выделения нет области
@msg Data Recovery has nowhere to keep copies: set Preferences → File Handling → Data Recovery → Folder (recoveryFolder) Восстановлению данных негде хранить копии: задайте Настройки → Работа с файлами → Восстановление данных → Папка (recoveryFolder)
@msg Data Recovery can't keep copies in {path}: {e} Восстановление данных не может хранить копии в {path}: {e}
@msg direct-select anchor points to cut at (not the end points of open paths) выделите прямым выделением опорные точки для разреза (не конечные точки открытых контуров)
@msg Divide Objects Below: select a path Разделение объектов ниже: выделите контур
@msg Divide Objects Below: select exactly one path Разделение объектов ниже: выделите ровно один контур
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3894,6 +3894,7 @@ axis Both Båda
@msg Create from Guides: the guides make more than {MAX_PIECES} slices Skapa från stödlinjer: stödlinjerna ger fler än {MAX_PIECES} skivor
@msg Create from Selection: the selection has no area Skapa från markering: markeringen har ingen area
@msg Data Recovery has nowhere to keep copies: set Preferences → File Handling → Data Recovery → Folder (recoveryFolder) Dataåterställning har ingenstans att spara kopior: ange Inställningar → Filhantering → Dataåterställning → Mapp (recoveryFolder)
@msg Data Recovery can't keep copies in {path}: {e} Dataåterställning kan inte spara kopior i {path}: {e}
@msg direct-select anchor points to cut at (not the end points of open paths) direktmarkera ankarpunkter att klippa vid (inte slutpunkterna på öppna banor)
@msg Divide Objects Below: select a path Dela objekt under: markera en bana
@msg Divide Objects Below: select exactly one path Dela objekt under: markera exakt en bana
Can't render this file because it contains an unexpected character in line 5 and column 25.
+1
View File
@@ -3778,6 +3778,7 @@ axis Both Her İkisi
@msg Create from Guides: the guides make more than {MAX_PIECES} slices Kılavuzlardan Oluşturun: kılavuzlar {MAX_PIECES} dilimlerinden daha fazlasını oluşturur
@msg Create from Selection: the selection has no area Seçimden Oluştur: Seçimin alanı yok
@msg Data Recovery has nowhere to keep copies: set Preferences → File Handling → Data Recovery → Folder (recoveryFolder) Veri Kurtarma'nın kopyaları saklayacak yeri yoktur: Tercihleri ayarlayın → Dosya İşleme → Veri Kurtarma → Klasör (recoveryFolder)
@msg Data Recovery can't keep copies in {path}: {e} Veri Kurtarma kopyaları {path} konumunda saklayamıyor: {e}
@msg direct-select anchor points to cut at (not the end points of open paths) kesilecek bağlantı noktalarını doğrudan seçin (açık yolların bitiş noktalarını değil)
@msg Divide Objects Below: select a path Aşağıdaki Nesneleri Böl: bir yol seçin
@msg Divide Objects Below: select exactly one path Aşağıdaki Nesneleri Böl: tam olarak bir yol seçin
Can't render this file because it contains an unexpected character in line 5 and column 25.
+1
View File
@@ -4009,6 +4009,7 @@ axis Both Обидві
@msg Create from Guides: the guides make more than {MAX_PIECES} slices Створення з напрямних: напрямні утворюють більше {MAX_PIECES} фрагментів
@msg Create from Selection: the selection has no area Створення з виділення: виділення немає області
@msg Data Recovery has nowhere to keep copies: set Preferences → File Handling → Data Recovery → Folder (recoveryFolder) Відновлення даних ніде зберігати копії: задайте Установки → Робота з файлами → Відновлення даних → Папка (recoveryFolder)
@msg Data Recovery can't keep copies in {path}: {e} Відновлення даних не може зберігати копії в {path}: {e}
@msg direct-select anchor points to cut at (not the end points of open paths) виділіть прямим виділенням опорні точки для розрізу (не кінцеві точки відкритих контурів)
@msg Divide Objects Below: select a path Поділ об’єктів нижче: виділіть контур
@msg Divide Objects Below: select exactly one path Поділ об’єктів нижче: виділіть рівно один контур
Can't render this file because it contains an unexpected character in line 177 and column 5.
+1
View File
@@ -4426,6 +4426,7 @@ theme Appearance Giao diện
@msg Create Outlines: select text objects Tạo đường bao: hãy chọn đối tượng văn bản
@msg damaged bị hỏng
@msg Data Recovery has nowhere to keep copies: set Preferences → File Handling → Data Recovery → Folder (recoveryFolder) Phục hồi dữ liệu không có nơi lưu bản sao: đặt Tùy chọn → Xử lý tệp → Phục hồi dữ liệu → Thư mục (recoveryFolder)
@msg Data Recovery can't keep copies in {path}: {e} Phục hồi dữ liệu không thể lưu bản sao vào {path}: {e}
@msg decimals must be {_1}–{_2} (got {_3}) số thập phân phải từ {_1}–{_2} (nhận được {_3})
@msg direct-select anchor points to cut at (not the end points of open paths) chọn trực tiếp các điểm neo để cắt (không phải điểm cuối của đường dẫn mở)
@msgplural Discarded {n} recovered document|Discarded {n} recovered documents Đã loại bỏ {n} tài liệu đã phục hồi
Can't render this file because it contains an unexpected character in line 199 and column 5.
+1
View File
@@ -4046,6 +4046,7 @@ imageTrace Outline 轮廓
@msg `{key}` must be one of {_1} `{key}` 必须是 {_1} 之一
@msg unknown preference `{key}` 未知首选项 `{key}`
@msg Data Recovery has nowhere to keep copies: set Preferences → File Handling → Data Recovery → Folder (recoveryFolder) 数据恢复没有可保存副本的位置:请设置“首选项 → 文件处理 → 数据恢复 → 文件夹” (recoveryFolder)
@msg Data Recovery can't keep copies in {path}: {e} 数据恢复无法将副本保存到 {path}:{e}
@msg `{area}` is no recovery area name `{area}` 不是恢复区域名称
@msg `{name}` is no recovery entry name `{name}` 不是恢复条目名称
@msg no clock to keep a heartbeat by 没有可用于维持心跳的时钟
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1932,6 +1932,7 @@ file-filter Templates 範本
@msg `{key}` must be one of {_1} `{key}` 必須是 {_1} 其中之一
@msg unknown preference `{key}` 未知的偏好設定 `{key}`
@msg Data Recovery has nowhere to keep copies: set Preferences → File Handling → Data Recovery → Folder (recoveryFolder) 「資料復原」沒有可存放拷貝的位置:請設定「偏好設定 → 檔案處理 → 資料復原 → 資料夾」 (recoveryFolder)
@msg Data Recovery can't keep copies in {path}: {e} 「資料復原」無法將拷貝存放到 {path}:{e}
@msg `{area}` is no recovery area name `{area}` 不是復原區域名稱
@msg `{name}` is no recovery entry name `{name}` 不是復原項目名稱
@msg no clock to keep a heartbeat by 沒有可用來維持心跳的時鐘
Can't render this file because it contains an unexpected character in line 6 and column 47.
+49 -2
View File
@@ -212,7 +212,7 @@ pub fn show(app: &mut VectorcraftApp, ctx: &egui::Context) {
ui.label(egui::RichText::new(tl!(cat)).font(theme::semibold(14.0)).color(t.text_strong));
ui.add_space(8.0);
egui::ScrollArea::vertical().id_salt(("prefs", cat)).max_height(400.0).auto_shrink([false, false]).show(ui, |ui| {
category_fields(ui, &mut d, cat);
category_fields(ui, app, &mut d, cat);
});
});
});
@@ -256,7 +256,7 @@ pub fn show(app: &mut VectorcraftApp, ctx: &egui::Context) {
}
}
fn category_fields(ui: &mut egui::Ui, d: &mut Dialog, cat: &str) {
fn category_fields(ui: &mut egui::Ui, app: &mut VectorcraftApp, d: &mut Dialog, cat: &str) {
let t = Tokens::get(ui.ctx());
let mut section = "";
let mut first = true;
@@ -386,6 +386,19 @@ fn category_fields(ui: &mut egui::Ui, d: &mut Dialog, cat: &str) {
}
});
}
// File Handling › Data Recovery › Folder: typed, or picked with Choose… where the host
// has a folder picker (not on the web).
PrefKind::Text if sp.key == "recoveryFolder" => {
labeled(ui, sp.label, |ui| {
let mut s = v.as_str().unwrap_or("").to_string();
if ui.add(egui::TextEdit::singleline(&mut s).align(egui::Align2::LEFT_CENTER).desired_width(200.0)).changed() {
d.fields.insert(sp.key.into(), json!(s));
}
if app.services.pick_folder.is_some() && ui.button(tl!("Choose…")).clicked() {
choose_recovery_folder(app, d);
}
});
}
PrefKind::Text => {
labeled(ui, sp.label, |ui| {
let mut s = v.as_str().unwrap_or("").to_string();
@@ -398,6 +411,40 @@ fn category_fields(ui: &mut egui::Ui, d: &mut Dialog, cat: &str) {
}
}
/// Data Recovery › Choose…: the folder picked becomes the Folder field's value when files can be
/// made in it. Otherwise the status bar says why and the field keeps its value, as it does when
/// the picker is cancelled.
fn choose_recovery_folder(app: &mut VectorcraftApp, d: &mut Dialog) {
let r = crate::picks::in_dialog(app, d, |app, d| {
let Some(path) = crate::picks::folder(app) else { return Ok(()) };
writable_folder(&path)?;
d.fields.insert("recoveryFolder".into(), json!(path));
Ok(())
});
if let Err(e) = r {
app.status(e);
}
}
/// `Ok` when `path` is a folder that a file can be created in: a file is made there and removed.
fn writable_folder(path: &str) -> Result<(), String> {
let dir = std::path::Path::new(path);
if !dir.is_dir() {
return Err(format!("{path} is not a folder"));
}
let nanos = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map_or(0, |t| t.as_nanos());
let check = dir.join(format!(".vectorcraft-write-check-{}-{nanos}", std::process::id()));
match std::fs::OpenOptions::new().write(true).create_new(true).open(&check) {
Ok(f) => {
drop(f);
// Best effort: the empty file is all there is to remove.
let _ = std::fs::remove_file(&check);
Ok(())
}
Err(e) => Err(format!("Data Recovery can't keep copies in {path}: {e}")),
}
}
/// Is one of the dialog's Units (General, Stroke, Type, East Asian Type) Picas?
fn picas_in_use(d: &Dialog) -> bool {
["unitsGeneral", "unitsStroke", "unitsType", "unitsAsianType"].iter().any(|k| d.str(k) == vectorcraft_doc::Unit::Picas.key())
+85
View File
@@ -424,3 +424,88 @@ fn use_system_defaults_for_edit_original_is_a_checkbox() {
confirm(&mut a).unwrap();
assert!(!a.session.prefs.edit_original_system_defaults);
}
/// A new, empty folder of its own for test `name`.
fn temp_folder(name: &str) -> std::path::PathBuf {
let dir = std::env::temp_dir().join(format!("vc-ui-prefs-{name}-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
dir
}
/// File Handling › Data Recovery › Folder has a Choose… button where the host has a folder
/// picker, and none where it has not (the web).
#[test]
fn recovery_folder_has_a_choose_button_with_a_folder_picker() {
let mut a = VectorcraftApp::new(Session::new(), Default::default());
a.services.pick_folder = Some(Box::new(|| None));
open(&mut a, Some("File Handling"));
let text = crate::tests_labels::painted_text(&mut a, |a, ui| show(a, ui.ctx()));
assert!(text.contains("Data Recovery") && text.contains("Choose…"), "{text}");
a.services.pick_folder = None;
let text = crate::tests_labels::painted_text(&mut a, |a, ui| show(a, ui.ctx()));
assert!(text.contains("Data Recovery") && !text.contains("Choose…"), "{text}");
}
/// Choose… puts the folder picked into the Folder field, and OK saves it; a cancelled picker
/// leaves the field as it was.
#[test]
fn a_chosen_recovery_folder_fills_the_field_and_cancel_keeps_it() {
let dir = temp_folder("recovery-chosen");
let picked = dir.to_string_lossy().into_owned();
let mut a = VectorcraftApp::new(Session::new(), Default::default());
open(&mut a, Some("File Handling"));
let answer = picked.clone();
a.services.pick_folder = Some(Box::new(move || Some(answer.clone())));
let mut d = a.ui.dialog.clone().unwrap();
choose_recovery_folder(&mut a, &mut d);
assert_eq!(d.str("recoveryFolder"), picked);
assert!(a.ui.status.is_empty(), "{}", a.ui.status);
assert_eq!(std::fs::read_dir(&dir).unwrap().count(), 0, "the write check leaves nothing behind");
a.services.pick_folder = Some(Box::new(|| None));
choose_recovery_folder(&mut a, &mut d);
assert_eq!(d.str("recoveryFolder"), picked, "cancel keeps the field");
a.ui.dialog = Some(d);
confirm(&mut a).unwrap();
assert_eq!(a.session.prefs.recovery_folder, picked);
let _ = std::fs::remove_dir_all(&dir);
}
/// A picked folder that no file can be created in is refused: the field keeps its value and the
/// status bar says why.
#[test]
fn a_recovery_folder_that_cannot_be_written_is_refused() {
let dir = temp_folder("recovery-refused");
let file = dir.join("not-a-folder.txt");
std::fs::write(&file, b"x").unwrap();
let mut a = VectorcraftApp::new(Session::new(), Default::default());
open(&mut a, Some("File Handling"));
let mut d = a.ui.dialog.clone().unwrap();
d.fields.insert("recoveryFolder".into(), json!("kept"));
for path in [file.clone(), dir.join("missing")] {
let answer = path.to_string_lossy().into_owned();
a.services.pick_folder = Some(Box::new(move || Some(answer.clone())));
a.ui.status.clear();
choose_recovery_folder(&mut a, &mut d);
assert_eq!(d.str("recoveryFolder"), "kept");
assert_eq!(a.ui.status, format!("{} is not a folder", path.to_string_lossy()));
}
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let locked = dir.join("locked");
std::fs::create_dir(&locked).unwrap();
std::fs::set_permissions(&locked, std::fs::Permissions::from_mode(0o555)).unwrap();
// An account that may write anyway (root) has nothing to refuse.
if std::fs::write(locked.join("probe"), b"").is_err() {
let answer = locked.to_string_lossy().into_owned();
a.services.pick_folder = Some(Box::new(move || Some(answer.clone())));
a.ui.status.clear();
choose_recovery_folder(&mut a, &mut d);
assert_eq!(d.str("recoveryFolder"), "kept");
assert!(a.ui.status.starts_with("Data Recovery can't keep copies in "), "{}", a.ui.status);
}
let _ = std::fs::set_permissions(&locked, std::fs::Permissions::from_mode(0o755));
}
let _ = std::fs::remove_dir_all(&dir);
}
+1
View File
@@ -1134,6 +1134,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
PDF export writes a linear or radial gradient that mixes a spot color with process colors, or two spot colors, as a DeviceN shading of the spot inks and the process inks its stops use, so each spot ink prints on its own plate; an off-center midpoint is written as a stop with the inks halfway between its two stops. A gradient that mixes a spot color with RGB or Lab colors is still written without the spot ink, with a warning. A Perceptual gradient of spot inks is written as a Separation or DeviceN shading of the stops on its curve; a dithered gradient fill, and a Perceptual gradient with RGB or Lab stops other than white or more than 4,096 stops on its curve, are written as images in process colors, with a warning. A Lab spot color in such a shading is named by the same Separation space, with the Lab alternate, as its flat fills (`engine/src/tests/linked_stops.rs`, `engine/src/tests/labspots.rs`).
PDF export writes a spot color named All or None (in a Separation space, the names of every plate and of no plate: [ISO 32000-1:2008](https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf), 8.6.6.4) as the ink All Spot or None Spot (All Spot 2 and on when a spot swatch already has that name) in its fills, strokes, gradients, colorized images and Lab alternates; Registration still prints on every plate (`pdf/src/tests/output.rs`).
- `.vectorcraft` (lossless JSON, compressed, atomic saves, save down to v1), Save As with a format chooser, Save a Copy, Revert, templates, Data Recovery after a crash (browser tabs hold their copies with Web Locks too, so a background tab's copies are never taken for a crash's, and a tab repairs copies another removed when it resumes: #367), background save and export. The desktop app writes its warnings and errors (graphics adapter, a lost graphics device, panics the guard recovered from) to standard error and to `logs/vectorcraft.log` next to the preferences, the two previous runs kept and each file capped at 16 MiB, so a launch without a terminal still leaves something to attach to a bug report (#465). Native documents with a lot of path data (traced or generated art, in layers, symbols or patterns) are saved as compact JSON, about a fifth of the size; small documents stay pretty-printed (#1492). Saving to a network drive on macOS falls back to a plain fsync when the share refuses a full flush (#1625, part of #1620; not confirmed on a real share), and a save still succeeds when a folder refuses the temporary file, the rename over the document or copying permissions: the content is written elsewhere first and copied in, and a failed copy keeps the temporary file and names it (#1649, part of #1620). A document opened by a relative path finds its relatively linked images, and `vectorcraft-cli convert` lists what reading the input left out as `importWarnings` (#1606, fixes #1604).
Preferences › File Handling › Data Recovery › Folder has a Choose… button on the desktop that picks the folder Data Recovery keeps its copies in ([Illustrator reference, Set Data Recovery preferences](https://web.archive.org/web/20210601162814/https://helpx.adobe.com/pdf/illustrator_reference.pdf)); a picked folder that a file cannot be created in is refused with a status message, and the field keeps its value.
A name typed in the desktop save panel without the file type's extension gets it, and when a file of that name exists, which the panel did not ask about, Save, Save As, Save a Copy, Save as Template, the exports and every other write through a save panel ask before replacing it (dialog `replaceFile`; Cancel writes nothing).
A name returned by the desktop save panel with the extension of a format the save does not write has it replaced with the saved format's: Save As of a new document saves `card.ait` and `card.png` as `card.vectorcraft`, and Save as Template saves `card.pdf` as `card.vctemplate`. An extension that names no format stays part of the name (`notes.v2` is saved as `notes.v2.vectorcraft`).
Export As, Save as PDF, Save for Office Documents, Save for Web, Text Export Options, print to file, the presets' Export… and the library, swatch and Document Info saves replace a typed extension of another format than the one they write in the same way: Export As PNG writes `card.svg` as `card.png`, Save as PDF writes `card.png` as `card.pdf`, and `document.exportSelection` with a `format` suggests and adds that format's extension. An extension of the written format stays as typed in any letter case (`photo.JPEG` for JPEG), Export As SVG writes `art.svgz` compressed, and `document.exportSelection` without a `format` writes the format the typed extension names (`tests/saveext.rs`).