mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-13 21:17:48 +00:00
Merge pull request #7085 from dotCooCoo/feat/learn-next-up
Learn: View more opens a tutorial's practice sample, and Next Up shows the next tutorial
This commit is contained in:
@@ -20,7 +20,7 @@ Every non-code asset in this repository (icons, images, fonts, example art, colo
|
||||
|
||||
**Policy (mandatory):** VectorCraft contains **no Adobe iconography, images, artwork, presets, swatch/brush/symbol/pattern libraries or ICC profiles**. Every asset is either original work by VectorCraft contributors, or third-party material under an open licence (OSI open source, public domain / CC0, or Creative Commons that allows redistribution), and it is attributed below. Screenshots of Adobe software are never committed. The one exception is the ArtCraft name, wordmark and logos in `docs/brand/`: ArtCraft trademarks, not open source, usable only under [`docs/brand/LICENSE-brand.txt`](docs/brand/LICENSE-brand.txt).
|
||||
|
||||
Generated-in-code art is original and has no file to list. This covers the default swatches, brushes, symbols, patterns, graphic styles, image-trace presets and the vector tool cursors (`crates/ui-egui/src/cursors.rs`).
|
||||
Generated-in-code art is original and has no file to list. This covers the default swatches, brushes, symbols, patterns, graphic styles, image-trace presets, the vector tool cursors (`crates/ui-egui/src/cursors.rs`) and the Learn panel's practice samples (`crates/ui-egui/src/panels/learn/samples.rs`).
|
||||
|
||||
**Font files are never committed here.** Fonts shared by the Crafting Apps live in [storytold/craft-fonts](https://github.com/storytold/craft-fonts). The small Latin UI/document fonts below predate that rule and stay.
|
||||
|
||||
|
||||
@@ -646,6 +646,12 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
|
||||
"",
|
||||
"{} the Learn panel's skills in the order its Skills tab lists them: {skills: [{id, title, tutorials: [{id, title, steps: number of steps}]}]}",
|
||||
),
|
||||
(
|
||||
"learn.skills",
|
||||
"Back to Skills",
|
||||
"",
|
||||
"{} show the Learn panel on its Skills tab with the list of skills, with no skill and no tutorial open, as Back to skills after a skill's last tutorial and the Quick skill guide card do; returns what learn.list returns",
|
||||
),
|
||||
(
|
||||
"learn.show",
|
||||
"Show Tutorial",
|
||||
@@ -658,6 +664,24 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
|
||||
"",
|
||||
"{tool: a tool id, as tool.select takes} mark where that tool is for 3 seconds without choosing it, as the link on a tool's name in a Learn tutorial step does: its toolbar button, a row of its collapsed group's flyout (which opens), its group's torn-off strip (which comes to the front), or, when no shown toolbar holds it, its row in the All Tools drawer (which opens); a Tools panel hidden with Window › Tools shows again; Escape or the next locate removes the mark; a missing, non-string or unknown id is an error and changes nothing; returns {tool, location: toolbar|flyout|strip|allTools|unavailable, toolbar: the name of the toolbar made with New Toolbar that holds it, or null}, with reason when Presentation Mode or Tab hides the toolbars",
|
||||
),
|
||||
(
|
||||
"learn.openSample",
|
||||
"Open Tutorial Sample",
|
||||
"",
|
||||
"{tutorial: id (see learn.list)} open that tutorial's practice sample as a new untitled document, which becomes the active one, and move the Learn view to the tutorial, as its View more button does; each call opens another copy and open documents stay as they are; a missing, non-string or unknown id is an error and changes nothing; returns {tutorial, index, title} of the new document",
|
||||
),
|
||||
(
|
||||
"learn.finish",
|
||||
"Finish Tutorial",
|
||||
"",
|
||||
"{tutorial: id (see learn.list)} mark a tutorial whose sample learn.openSample opened as finished, as its Finish tutorial button does; finishing it again keeps one entry; a missing, non-string or unknown id, or a tutorial whose sample was not opened, is an error and changes nothing; returns {tutorial, finished: true, next: id of the next tutorial in its skill, or null after the skill's last one}",
|
||||
),
|
||||
(
|
||||
"learn.next",
|
||||
"Next Tutorial",
|
||||
"",
|
||||
"{tutorial: id (see learn.list)} show the Learn panel on the tutorial after a finished one in the same skill, as its Next Up button does, without opening a sample; a missing, non-string or unknown id, a tutorial not finished, or a skill's last tutorial is an error and changes nothing; returns {skill, tutorial, title} of the next tutorial",
|
||||
),
|
||||
("window.brightness", "UI Brightness", "", "{brightness: dark|mediumDark|mediumLight|light}"),
|
||||
(
|
||||
"window.workspace",
|
||||
|
||||
@@ -6751,6 +6751,19 @@ breakText Glyphs Глифи
|
||||
Show where this tool is Показване къде е този инструмент
|
||||
@msg missing `tutorial` (see learn.list) липсва `tutorial` (вижте learn.list)
|
||||
@msg unknown tutorial `{id}` (see learn.list) непознат урок `{id}` (вижте learn.list)
|
||||
View more Вижте повече
|
||||
Finish tutorial Завършване на урока
|
||||
Next Up Следващ
|
||||
You finished the last tutorial in this skill. Завършихте последния урок в това умение.
|
||||
Back to skills Обратно към уменията
|
||||
Open Tutorial Sample Отваряне на примерния файл на урока
|
||||
Finish Tutorial Завършване на урок
|
||||
Next Tutorial Следващ урок
|
||||
Back to Skills Обратно към уменията
|
||||
@msg tutorial `{id}` has no practice sample урокът `{id}` няма примерен файл за упражнение
|
||||
@msg open the practice sample of `{id}` first (see learn.openSample) първо отворете примерния файл за упражнение на `{id}` (вижте learn.openSample)
|
||||
@msg finish `{id}` first (see learn.finish) първо завършете `{id}` (вижте learn.finish)
|
||||
@msg `{id}` is the last tutorial in its skill `{id}` е последният урок в своето умение
|
||||
|
||||
# Deferred plug-in effects
|
||||
Refresh Deferred Effects Обновяване на отложените ефекти
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -6805,6 +6805,19 @@ shape Open Otvoren
|
||||
Show where this tool is Prikaži gdje je ovaj alat
|
||||
@msg missing `tutorial` (see learn.list) nedostaje `tutorial` (vidi learn.list)
|
||||
@msg unknown tutorial `{id}` (see learn.list) nepoznat tutorijal `{id}` (vidi learn.list)
|
||||
View more Prikaži više
|
||||
Finish tutorial Završi tutorijal
|
||||
Next Up Sljedeće
|
||||
You finished the last tutorial in this skill. Završili ste posljednji tutorijal u ovoj vještini.
|
||||
Back to skills Nazad na vještine
|
||||
Open Tutorial Sample Otvori uzorak tutorijala
|
||||
Finish Tutorial Završi tutorijal
|
||||
Next Tutorial Sljedeći tutorijal
|
||||
Back to Skills Nazad na vještine
|
||||
@msg tutorial `{id}` has no practice sample tutorijal `{id}` nema uzorak za vježbu
|
||||
@msg open the practice sample of `{id}` first (see learn.openSample) prvo otvorite uzorak za vježbu tutorijala `{id}` (vidi learn.openSample)
|
||||
@msg finish `{id}` first (see learn.finish) prvo završite `{id}` (vidi learn.finish)
|
||||
@msg `{id}` is the last tutorial in its skill `{id}` je posljednji tutorijal u svojoj vještini
|
||||
|
||||
# Deferred plug-in effects
|
||||
Refresh Deferred Effects Osvježi odgođene efekte
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -7328,6 +7328,19 @@ breakText Glyphs Glifs
|
||||
Show where this tool is Mostra on és aquesta eina
|
||||
@msg missing `tutorial` (see learn.list) falta `tutorial` (vegeu learn.list)
|
||||
@msg unknown tutorial `{id}` (see learn.list) tutorial desconegut `{id}` (vegeu learn.list)
|
||||
View more Mostra’n més
|
||||
Finish tutorial Acaba el tutorial
|
||||
Next Up A continuació
|
||||
You finished the last tutorial in this skill. Heu acabat l’últim tutorial d’aquesta habilitat.
|
||||
Back to skills Torna a les habilitats
|
||||
Open Tutorial Sample Obre la mostra del tutorial
|
||||
Finish Tutorial Acaba el tutorial
|
||||
Next Tutorial Tutorial següent
|
||||
Back to Skills Torna a les habilitats
|
||||
@msg tutorial `{id}` has no practice sample el tutorial `{id}` no té cap mostra de pràctica
|
||||
@msg open the practice sample of `{id}` first (see learn.openSample) obriu primer la mostra de pràctica de `{id}` (vegeu learn.openSample)
|
||||
@msg finish `{id}` first (see learn.finish) acabeu primer `{id}` (vegeu learn.finish)
|
||||
@msg `{id}` is the last tutorial in its skill `{id}` és l’últim tutorial de la seva habilitat
|
||||
|
||||
# Deferred plug-in effects
|
||||
Refresh Deferred Effects Actualitza els efectes ajornats
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -7347,6 +7347,19 @@ breakText Glyphs Glyfy
|
||||
Show where this tool is Zobrazit, kde je tento nástroj
|
||||
@msg missing `tutorial` (see learn.list) chybí `tutorial` (viz learn.list)
|
||||
@msg unknown tutorial `{id}` (see learn.list) neznámý výukový kurz `{id}` (viz learn.list)
|
||||
View more Zobrazit více
|
||||
Finish tutorial Dokončit výukový kurz
|
||||
Next Up Další
|
||||
You finished the last tutorial in this skill. Dokončili jste poslední výukový kurz této dovednosti.
|
||||
Back to skills Zpět na dovednosti
|
||||
Open Tutorial Sample Otevřít ukázku výukového kurzu
|
||||
Finish Tutorial Dokončit výukový kurz
|
||||
Next Tutorial Další výukový kurz
|
||||
Back to Skills Zpět na dovednosti
|
||||
@msg tutorial `{id}` has no practice sample výukový kurz `{id}` nemá cvičnou ukázku
|
||||
@msg open the practice sample of `{id}` first (see learn.openSample) nejprve otevřete cvičnou ukázku kurzu `{id}` (viz learn.openSample)
|
||||
@msg finish `{id}` first (see learn.finish) nejprve dokončete `{id}` (viz learn.finish)
|
||||
@msg `{id}` is the last tutorial in its skill `{id}` je poslední výukový kurz své dovednosti
|
||||
|
||||
# Deferred plug-in effects
|
||||
Refresh Deferred Effects Obnovit odložené efekty
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -6767,6 +6767,19 @@ breakText Glyphs Glyffer
|
||||
Show where this tool is Vis, hvor dette værktøj er
|
||||
@msg missing `tutorial` (see learn.list) `tutorial` mangler (se learn.list)
|
||||
@msg unknown tutorial `{id}` (see learn.list) ukendt vejledning `{id}` (se learn.list)
|
||||
View more Vis mere
|
||||
Finish tutorial Afslut vejledning
|
||||
Next Up Næste
|
||||
You finished the last tutorial in this skill. Du har afsluttet den sidste vejledning i denne færdighed.
|
||||
Back to skills Tilbage til færdigheder
|
||||
Open Tutorial Sample Åbn eksempel til vejledning
|
||||
Finish Tutorial Afslut vejledning
|
||||
Next Tutorial Næste vejledning
|
||||
Back to Skills Tilbage til færdigheder
|
||||
@msg tutorial `{id}` has no practice sample vejledningen `{id}` har intet øvelseseksempel
|
||||
@msg open the practice sample of `{id}` first (see learn.openSample) åbn først øvelseseksemplet til `{id}` (se learn.openSample)
|
||||
@msg finish `{id}` first (see learn.finish) afslut først `{id}` (se learn.finish)
|
||||
@msg `{id}` is the last tutorial in its skill `{id}` er den sidste vejledning i sin færdighed
|
||||
|
||||
# Deferred plug-in effects
|
||||
Refresh Deferred Effects Opdatér udskudte effekter
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -6942,6 +6942,19 @@ breakText Glyphs Glyphen
|
||||
Show where this tool is Zeigen, wo sich dieses Werkzeug befindet
|
||||
@msg missing `tutorial` (see learn.list) `tutorial` fehlt (siehe learn.list)
|
||||
@msg unknown tutorial `{id}` (see learn.list) unbekanntes Lernprogramm `{id}` (siehe learn.list)
|
||||
View more Mehr anzeigen
|
||||
Finish tutorial Lernprogramm abschließen
|
||||
Next Up Als Nächstes
|
||||
You finished the last tutorial in this skill. Sie haben das letzte Lernprogramm dieser Fertigkeit abgeschlossen.
|
||||
Back to skills Zurück zu den Fertigkeiten
|
||||
Open Tutorial Sample Übungsdatei des Lernprogramms öffnen
|
||||
Finish Tutorial Lernprogramm abschließen
|
||||
Next Tutorial Nächstes Lernprogramm
|
||||
Back to Skills Zurück zu den Fertigkeiten
|
||||
@msg tutorial `{id}` has no practice sample das Lernprogramm `{id}` hat keine Übungsdatei
|
||||
@msg open the practice sample of `{id}` first (see learn.openSample) öffnen Sie zuerst die Übungsdatei von `{id}` (siehe learn.openSample)
|
||||
@msg finish `{id}` first (see learn.finish) schließen Sie zuerst `{id}` ab (siehe learn.finish)
|
||||
@msg `{id}` is the last tutorial in its skill `{id}` ist das letzte Lernprogramm seiner Fertigkeit
|
||||
|
||||
# Deferred plug-in effects
|
||||
Refresh Deferred Effects Zurückgestellte Effekte aktualisieren
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -7299,6 +7299,19 @@ breakText Glyphs Γλύφοι
|
||||
Show where this tool is Εμφάνιση της θέσης αυτού του εργαλείου
|
||||
@msg missing `tutorial` (see learn.list) λείπει το `tutorial` (δείτε learn.list)
|
||||
@msg unknown tutorial `{id}` (see learn.list) άγνωστος οδηγός εκμάθησης `{id}` (δείτε learn.list)
|
||||
View more Προβολή περισσότερων
|
||||
Finish tutorial Ολοκλήρωση οδηγού
|
||||
Next Up Επόμενο
|
||||
You finished the last tutorial in this skill. Ολοκληρώσατε τον τελευταίο οδηγό εκμάθησης αυτής της δεξιότητας.
|
||||
Back to skills Πίσω στις δεξιότητες
|
||||
Open Tutorial Sample Άνοιγμα δείγματος οδηγού εκμάθησης
|
||||
Finish Tutorial Ολοκλήρωση οδηγού εκμάθησης
|
||||
Next Tutorial Επόμενος οδηγός εκμάθησης
|
||||
Back to Skills Πίσω στις δεξιότητες
|
||||
@msg tutorial `{id}` has no practice sample ο οδηγός εκμάθησης `{id}` δεν έχει δείγμα εξάσκησης
|
||||
@msg open the practice sample of `{id}` first (see learn.openSample) ανοίξτε πρώτα το δείγμα εξάσκησης του `{id}` (δείτε learn.openSample)
|
||||
@msg finish `{id}` first (see learn.finish) ολοκληρώστε πρώτα το `{id}` (δείτε learn.finish)
|
||||
@msg `{id}` is the last tutorial in its skill το `{id}` είναι ο τελευταίος οδηγός εκμάθησης της δεξιότητάς του
|
||||
|
||||
# Deferred plug-in effects
|
||||
Refresh Deferred Effects Ανανέωση εφέ σε αναμονή
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -7290,6 +7290,19 @@ breakText Glyphs Glifos
|
||||
Show where this tool is Mostrar dónde está esta herramienta
|
||||
@msg missing `tutorial` (see learn.list) falta `tutorial` (consulta learn.list)
|
||||
@msg unknown tutorial `{id}` (see learn.list) tutorial desconocido `{id}` (consulta learn.list)
|
||||
View more Ver más
|
||||
Finish tutorial Terminar tutorial
|
||||
Next Up A continuación
|
||||
You finished the last tutorial in this skill. Has terminado el último tutorial de esta habilidad.
|
||||
Back to skills Volver a las habilidades
|
||||
Open Tutorial Sample Abrir muestra del tutorial
|
||||
Finish Tutorial Terminar tutorial
|
||||
Next Tutorial Tutorial siguiente
|
||||
Back to Skills Volver a las habilidades
|
||||
@msg tutorial `{id}` has no practice sample el tutorial `{id}` no tiene muestra de práctica
|
||||
@msg open the practice sample of `{id}` first (see learn.openSample) abre primero la muestra de práctica de `{id}` (consulta learn.openSample)
|
||||
@msg finish `{id}` first (see learn.finish) termina primero `{id}` (consulta learn.finish)
|
||||
@msg `{id}` is the last tutorial in its skill `{id}` es el último tutorial de su habilidad
|
||||
|
||||
# Deferred plug-in effects
|
||||
Refresh Deferred Effects Actualizar efectos aplazados
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -7382,6 +7382,19 @@ breakText Glyphs Merkit
|
||||
Show where this tool is Näytä, missä tämä työkalu on
|
||||
@msg missing `tutorial` (see learn.list) `tutorial` puuttuu (ks. learn.list)
|
||||
@msg unknown tutorial `{id}` (see learn.list) tuntematon opetusohjelma `{id}` (ks. learn.list)
|
||||
View more Näytä lisää
|
||||
Finish tutorial Viimeistele opetusohjelma
|
||||
Next Up Seuraavaksi
|
||||
You finished the last tutorial in this skill. Viimeistelit tämän taidon viimeisen opetusohjelman.
|
||||
Back to skills Takaisin taitoihin
|
||||
Open Tutorial Sample Avaa opetusohjelman harjoitustiedosto
|
||||
Finish Tutorial Viimeistele opetusohjelma
|
||||
Next Tutorial Seuraava opetusohjelma
|
||||
Back to Skills Takaisin taitoihin
|
||||
@msg tutorial `{id}` has no practice sample opetusohjelmalla `{id}` ei ole harjoitustiedostoa
|
||||
@msg open the practice sample of `{id}` first (see learn.openSample) avaa ensin opetusohjelman `{id}` harjoitustiedosto (ks. learn.openSample)
|
||||
@msg finish `{id}` first (see learn.finish) viimeistele ensin `{id}` (ks. learn.finish)
|
||||
@msg `{id}` is the last tutorial in its skill `{id}` on taitonsa viimeinen opetusohjelma
|
||||
|
||||
# Deferred plug-in effects
|
||||
Refresh Deferred Effects Päivitä lykätyt tehosteet
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -7342,6 +7342,19 @@ breakText Glyphs Glyphes
|
||||
Show where this tool is Afficher l'emplacement de cet outil
|
||||
@msg missing `tutorial` (see learn.list) `tutorial` manquant (voir learn.list)
|
||||
@msg unknown tutorial `{id}` (see learn.list) tutoriel inconnu `{id}` (voir learn.list)
|
||||
View more Voir plus
|
||||
Finish tutorial Terminer le tutoriel
|
||||
Next Up À suivre
|
||||
You finished the last tutorial in this skill. Vous avez terminé le dernier tutoriel de cette compétence.
|
||||
Back to skills Retour aux compétences
|
||||
Open Tutorial Sample Ouvrir le fichier d’exercice du tutoriel
|
||||
Finish Tutorial Terminer le tutoriel
|
||||
Next Tutorial Tutoriel suivant
|
||||
Back to Skills Retour aux compétences
|
||||
@msg tutorial `{id}` has no practice sample le tutoriel `{id}` n’a pas de fichier d’exercice
|
||||
@msg open the practice sample of `{id}` first (see learn.openSample) ouvrez d’abord le fichier d’exercice de `{id}` (voir learn.openSample)
|
||||
@msg finish `{id}` first (see learn.finish) terminez d’abord `{id}` (voir learn.finish)
|
||||
@msg `{id}` is the last tutorial in its skill `{id}` est le dernier tutoriel de sa compétence
|
||||
|
||||
# Deferred plug-in effects
|
||||
Refresh Deferred Effects Actualiser les effets différés
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -8740,6 +8740,19 @@ theme Appearance अपीयरेंस
|
||||
Show where this tool is दिखाएँ कि यह टूल कहाँ है
|
||||
@msg missing `tutorial` (see learn.list) लापता `tutorial` (learn.list देखें)
|
||||
@msg unknown tutorial `{id}` (see learn.list) अज्ञात ट्यूटोरियल `{id}` (learn.list देखें)
|
||||
View more और देखें
|
||||
Finish tutorial ट्यूटोरियल पूरा करें
|
||||
Next Up आगे
|
||||
You finished the last tutorial in this skill. आपने इस कौशल का अंतिम ट्यूटोरियल पूरा कर लिया है।
|
||||
Back to skills कौशल पर वापस जाएँ
|
||||
Open Tutorial Sample ट्यूटोरियल का नमूना खोलें
|
||||
Finish Tutorial ट्यूटोरियल पूरा करें
|
||||
Next Tutorial अगला ट्यूटोरियल
|
||||
Back to Skills कौशल पर वापस जाएँ
|
||||
@msg tutorial `{id}` has no practice sample ट्यूटोरियल `{id}` का कोई अभ्यास नमूना नहीं है
|
||||
@msg open the practice sample of `{id}` first (see learn.openSample) पहले `{id}` का अभ्यास नमूना खोलें (learn.openSample देखें)
|
||||
@msg finish `{id}` first (see learn.finish) पहले `{id}` पूरा करें (learn.finish देखें)
|
||||
@msg `{id}` is the last tutorial in its skill `{id}` अपने कौशल का अंतिम ट्यूटोरियल है
|
||||
|
||||
# Deferred plug-in effects
|
||||
Refresh Deferred Effects स्थगित इफ़ेक्ट्स रिफ्रेश करें
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -7331,6 +7331,19 @@ breakText Glyphs Glifák
|
||||
Show where this tool is Megmutatja, hol van ez az eszköz
|
||||
@msg missing `tutorial` (see learn.list) hiányzik: `tutorial` (lásd: learn.list)
|
||||
@msg unknown tutorial `{id}` (see learn.list) ismeretlen oktatóanyag: `{id}` (lásd: learn.list)
|
||||
View more Továbbiak
|
||||
Finish tutorial Oktatóanyag befejezése
|
||||
Next Up Következő
|
||||
You finished the last tutorial in this skill. Befejezte a készség utolsó oktatóanyagát.
|
||||
Back to skills Vissza a készségekhez
|
||||
Open Tutorial Sample Oktatóanyag mintafájljának megnyitása
|
||||
Finish Tutorial Oktatóanyag befejezése
|
||||
Next Tutorial Következő oktatóanyag
|
||||
Back to Skills Vissza a készségekhez
|
||||
@msg tutorial `{id}` has no practice sample nincs gyakorló mintafájl ehhez az oktatóanyaghoz: `{id}`
|
||||
@msg open the practice sample of `{id}` first (see learn.openSample) előbb nyissa meg a gyakorló mintafájlt ehhez: `{id}` (lásd: learn.openSample)
|
||||
@msg finish `{id}` first (see learn.finish) előbb fejezze be ezt: `{id}` (lásd: learn.finish)
|
||||
@msg `{id}` is the last tutorial in its skill a készsége utolsó oktatóanyaga: `{id}`
|
||||
|
||||
# Deferred plug-in effects
|
||||
Refresh Deferred Effects Elhalasztott effektusok frissítése
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -6767,6 +6767,19 @@ breakText Glyphs Glif
|
||||
Show where this tool is Tampilkan letak alat ini
|
||||
@msg missing `tutorial` (see learn.list) `tutorial` tidak ada (lihat learn.list)
|
||||
@msg unknown tutorial `{id}` (see learn.list) tutorial `{id}` tidak dikenal (lihat learn.list)
|
||||
View more Lihat selengkapnya
|
||||
Finish tutorial Selesaikan tutorial
|
||||
Next Up Berikutnya
|
||||
You finished the last tutorial in this skill. Anda telah menyelesaikan tutorial terakhir dalam keterampilan ini.
|
||||
Back to skills Kembali ke keterampilan
|
||||
Open Tutorial Sample Buka contoh tutorial
|
||||
Finish Tutorial Selesaikan tutorial
|
||||
Next Tutorial Tutorial berikutnya
|
||||
Back to Skills Kembali ke keterampilan
|
||||
@msg tutorial `{id}` has no practice sample tutorial `{id}` tidak memiliki contoh latihan
|
||||
@msg open the practice sample of `{id}` first (see learn.openSample) buka contoh latihan `{id}` terlebih dahulu (lihat learn.openSample)
|
||||
@msg finish `{id}` first (see learn.finish) selesaikan `{id}` terlebih dahulu (lihat learn.finish)
|
||||
@msg `{id}` is the last tutorial in its skill `{id}` adalah tutorial terakhir dalam keterampilannya
|
||||
|
||||
# Deferred plug-in effects
|
||||
Refresh Deferred Effects Segarkan efek yang ditunda
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2418 and column 5.
|
@@ -6976,6 +6976,19 @@ breakText Glyphs Glifi
|
||||
Show where this tool is Mostra dove si trova questo strumento
|
||||
@msg missing `tutorial` (see learn.list) manca `tutorial` (vedi learn.list)
|
||||
@msg unknown tutorial `{id}` (see learn.list) tutorial sconosciuto `{id}` (vedi learn.list)
|
||||
View more Mostra altro
|
||||
Finish tutorial Termina il tutorial
|
||||
Next Up Prossimo
|
||||
You finished the last tutorial in this skill. Hai terminato l’ultimo tutorial di questa competenza.
|
||||
Back to skills Torna alle competenze
|
||||
Open Tutorial Sample Apri l’esempio del tutorial
|
||||
Finish Tutorial Termina il tutorial
|
||||
Next Tutorial Tutorial successivo
|
||||
Back to Skills Torna alle competenze
|
||||
@msg tutorial `{id}` has no practice sample il tutorial `{id}` non ha un esempio di esercitazione
|
||||
@msg open the practice sample of `{id}` first (see learn.openSample) apri prima l’esempio di esercitazione di `{id}` (vedi learn.openSample)
|
||||
@msg finish `{id}` first (see learn.finish) termina prima `{id}` (vedi learn.finish)
|
||||
@msg `{id}` is the last tutorial in its skill `{id}` è l’ultimo tutorial della sua competenza
|
||||
|
||||
# Deferred plug-in effects
|
||||
Refresh Deferred Effects Aggiorna effetti rimandati
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -7041,6 +7041,19 @@ breakText Glyphs 字形
|
||||
Show where this tool is このツールの場所を表示
|
||||
@msg missing `tutorial` (see learn.list) `tutorial` が指定されていません (learn.list を参照)
|
||||
@msg unknown tutorial `{id}` (see learn.list) `{id}` は不明なチュートリアルです (learn.list を参照)
|
||||
View more 詳細を見る
|
||||
Finish tutorial チュートリアルを完了
|
||||
Next Up 次へ
|
||||
You finished the last tutorial in this skill. このスキルの最後のチュートリアルを完了しました。
|
||||
Back to skills スキルに戻る
|
||||
Open Tutorial Sample チュートリアルのサンプルを開く
|
||||
Finish Tutorial チュートリアルを完了
|
||||
Next Tutorial 次のチュートリアル
|
||||
Back to Skills スキルに戻る
|
||||
@msg tutorial `{id}` has no practice sample チュートリアル `{id}` には練習用サンプルがありません
|
||||
@msg open the practice sample of `{id}` first (see learn.openSample) 先に `{id}` の練習用サンプルを開いてください (learn.openSample を参照)
|
||||
@msg finish `{id}` first (see learn.finish) 先に `{id}` を完了してください (learn.finish を参照)
|
||||
@msg `{id}` is the last tutorial in its skill `{id}` はこのスキルの最後のチュートリアルです
|
||||
|
||||
# Deferred plug-in effects
|
||||
Refresh Deferred Effects 保留中の効果を更新
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -7461,6 +7461,19 @@ font-script Japanese იაპონური
|
||||
Show where this tool is ამ ხელსაწყოს მდებარეობის ჩვენება
|
||||
@msg missing `tutorial` (see learn.list) აკლია `tutorial` (იხ. learn.list)
|
||||
@msg unknown tutorial `{id}` (see learn.list) უცნობი გაკვეთილი `{id}` (იხ. learn.list)
|
||||
View more მეტის ნახვა
|
||||
Finish tutorial გაკვეთილის დასრულება
|
||||
Next Up შემდეგი
|
||||
You finished the last tutorial in this skill. თქვენ დაასრულეთ ამ უნარის ბოლო გაკვეთილი.
|
||||
Back to skills უნარებზე დაბრუნება
|
||||
Open Tutorial Sample გაკვეთილის ნიმუშის გახსნა
|
||||
Finish Tutorial გაკვეთილის დასრულება
|
||||
Next Tutorial შემდეგი გაკვეთილი
|
||||
Back to Skills უნარებზე დაბრუნება
|
||||
@msg tutorial `{id}` has no practice sample გაკვეთილს `{id}` არ აქვს სავარჯიშო ნიმუში
|
||||
@msg open the practice sample of `{id}` first (see learn.openSample) ჯერ გახსენით `{id}` გაკვეთილის სავარჯიშო ნიმუში (იხ. learn.openSample)
|
||||
@msg finish `{id}` first (see learn.finish) ჯერ დაასრულეთ `{id}` (იხ. learn.finish)
|
||||
@msg `{id}` is the last tutorial in its skill `{id}` თავისი უნარის ბოლო გაკვეთილია
|
||||
|
||||
# Deferred plug-in effects
|
||||
Refresh Deferred Effects გადადებული ეფექტების განახლება
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -6937,6 +6937,19 @@ breakText Glyphs 글리프
|
||||
Show where this tool is 이 도구의 위치 표시
|
||||
@msg missing `tutorial` (see learn.list) `tutorial`이 없습니다(learn.list 참조)
|
||||
@msg unknown tutorial `{id}` (see learn.list) 알 수 없는 튜토리얼 `{id}`(learn.list 참조)
|
||||
View more 자세히 보기
|
||||
Finish tutorial 튜토리얼 완료
|
||||
Next Up 다음
|
||||
You finished the last tutorial in this skill. 이 기술의 마지막 튜토리얼을 완료했습니다.
|
||||
Back to skills 기술로 돌아가기
|
||||
Open Tutorial Sample 튜토리얼 샘플 열기
|
||||
Finish Tutorial 튜토리얼 완료
|
||||
Next Tutorial 다음 튜토리얼
|
||||
Back to Skills 기술로 돌아가기
|
||||
@msg tutorial `{id}` has no practice sample 튜토리얼 `{id}`에 연습용 샘플이 없습니다
|
||||
@msg open the practice sample of `{id}` first (see learn.openSample) 먼저 `{id}`의 연습용 샘플을 여세요(learn.openSample 참조)
|
||||
@msg finish `{id}` first (see learn.finish) 먼저 `{id}`을(를) 완료하세요(learn.finish 참조)
|
||||
@msg `{id}` is the last tutorial in its skill `{id}`은(는) 해당 기술의 마지막 튜토리얼입니다
|
||||
|
||||
# Deferred plug-in effects
|
||||
Refresh Deferred Effects 보류된 효과 새로 고침
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -7380,6 +7380,19 @@ breakText Glyphs Glyphs
|
||||
Show where this tool is Tonen waar dit gereedschap zich bevindt
|
||||
@msg missing `tutorial` (see learn.list) `tutorial` ontbreekt (zie learn.list)
|
||||
@msg unknown tutorial `{id}` (see learn.list) onbekende tutorial `{id}` (zie learn.list)
|
||||
View more Meer weergeven
|
||||
Finish tutorial Tutorial voltooien
|
||||
Next Up Hierna
|
||||
You finished the last tutorial in this skill. Je hebt de laatste tutorial van deze vaardigheid voltooid.
|
||||
Back to skills Terug naar vaardigheden
|
||||
Open Tutorial Sample Oefenbestand van tutorial openen
|
||||
Finish Tutorial Tutorial voltooien
|
||||
Next Tutorial Volgende tutorial
|
||||
Back to Skills Terug naar vaardigheden
|
||||
@msg tutorial `{id}` has no practice sample tutorial `{id}` heeft geen oefenbestand
|
||||
@msg open the practice sample of `{id}` first (see learn.openSample) open eerst het oefenbestand van `{id}` (zie learn.openSample)
|
||||
@msg finish `{id}` first (see learn.finish) voltooi eerst `{id}` (zie learn.finish)
|
||||
@msg `{id}` is the last tutorial in its skill `{id}` is de laatste tutorial van zijn vaardigheid
|
||||
|
||||
# Deferred plug-in effects
|
||||
Refresh Deferred Effects Uitgestelde effecten vernieuwen
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -7203,6 +7203,19 @@ breakText Glyphs Glify
|
||||
Show where this tool is Pokaż, gdzie jest to narzędzie
|
||||
@msg missing `tutorial` (see learn.list) brak `tutorial` (patrz learn.list)
|
||||
@msg unknown tutorial `{id}` (see learn.list) nieznany samouczek `{id}` (patrz learn.list)
|
||||
View more Pokaż więcej
|
||||
Finish tutorial Zakończ samouczek
|
||||
Next Up Następny
|
||||
You finished the last tutorial in this skill. Ukończono ostatni samouczek tej umiejętności.
|
||||
Back to skills Powrót do umiejętności
|
||||
Open Tutorial Sample Otwórz przykład samouczka
|
||||
Finish Tutorial Zakończ samouczek
|
||||
Next Tutorial Następny samouczek
|
||||
Back to Skills Powrót do umiejętności
|
||||
@msg tutorial `{id}` has no practice sample samouczek `{id}` nie ma przykładu do ćwiczeń
|
||||
@msg open the practice sample of `{id}` first (see learn.openSample) najpierw otwórz przykład do ćwiczeń samouczka `{id}` (patrz learn.openSample)
|
||||
@msg finish `{id}` first (see learn.finish) najpierw zakończ `{id}` (patrz learn.finish)
|
||||
@msg `{id}` is the last tutorial in its skill `{id}` to ostatni samouczek swojej umiejętności
|
||||
|
||||
# Deferred plug-in effects
|
||||
Refresh Deferred Effects Odśwież odroczone efekty
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -7420,6 +7420,19 @@ swatch Yellow Green Verde-amarelado
|
||||
Show where this tool is Mostrar onde está esta ferramenta
|
||||
@msg missing `tutorial` (see learn.list) falta `tutorial` (veja learn.list)
|
||||
@msg unknown tutorial `{id}` (see learn.list) tutorial desconhecido `{id}` (veja learn.list)
|
||||
View more Ver mais
|
||||
Finish tutorial Concluir tutorial
|
||||
Next Up A seguir
|
||||
You finished the last tutorial in this skill. Você concluiu o último tutorial desta habilidade.
|
||||
Back to skills Voltar às habilidades
|
||||
Open Tutorial Sample Abrir Amostra do Tutorial
|
||||
Finish Tutorial Concluir Tutorial
|
||||
Next Tutorial Próximo Tutorial
|
||||
Back to Skills Voltar às Habilidades
|
||||
@msg tutorial `{id}` has no practice sample o tutorial `{id}` não tem amostra de prática
|
||||
@msg open the practice sample of `{id}` first (see learn.openSample) abra primeiro a amostra de prática de `{id}` (veja learn.openSample)
|
||||
@msg finish `{id}` first (see learn.finish) conclua primeiro `{id}` (veja learn.finish)
|
||||
@msg `{id}` is the last tutorial in its skill `{id}` é o último tutorial da sua habilidade
|
||||
|
||||
# Deferred plug-in effects
|
||||
Refresh Deferred Effects Atualizar Efeitos Adiados
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -7436,6 +7436,19 @@ shape Open Открытый
|
||||
Show where this tool is Показать, где находится этот инструмент
|
||||
@msg missing `tutorial` (see learn.list) отсутствует `tutorial` (см. learn.list)
|
||||
@msg unknown tutorial `{id}` (see learn.list) неизвестный урок `{id}` (см. learn.list)
|
||||
View more Подробнее
|
||||
Finish tutorial Завершить урок
|
||||
Next Up Далее
|
||||
You finished the last tutorial in this skill. Вы завершили последний урок этого навыка.
|
||||
Back to skills Назад к навыкам
|
||||
Open Tutorial Sample Открыть пример урока
|
||||
Finish Tutorial Завершить урок
|
||||
Next Tutorial Следующий урок
|
||||
Back to Skills Назад к навыкам
|
||||
@msg tutorial `{id}` has no practice sample у урока `{id}` нет учебного примера
|
||||
@msg open the practice sample of `{id}` first (see learn.openSample) сначала откройте учебный пример урока `{id}` (см. learn.openSample)
|
||||
@msg finish `{id}` first (see learn.finish) сначала завершите `{id}` (см. learn.finish)
|
||||
@msg `{id}` is the last tutorial in its skill `{id}` является последним уроком своего навыка
|
||||
|
||||
# Deferred plug-in effects
|
||||
Refresh Deferred Effects Обновить отложенные эффекты
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -8786,6 +8786,19 @@ digits Hindi Hindi
|
||||
Show where this tool is Visa var det här verktyget finns
|
||||
@msg missing `tutorial` (see learn.list) `tutorial` saknas (se learn.list)
|
||||
@msg unknown tutorial `{id}` (see learn.list) okänd självstudie `{id}` (se learn.list)
|
||||
View more Visa mer
|
||||
Finish tutorial Slutför självstudien
|
||||
Next Up Nästa
|
||||
You finished the last tutorial in this skill. Du har slutfört den sista självstudien i den här färdigheten.
|
||||
Back to skills Tillbaka till färdigheter
|
||||
Open Tutorial Sample Öppna övningsexempel för självstudie
|
||||
Finish Tutorial Slutför självstudie
|
||||
Next Tutorial Nästa självstudie
|
||||
Back to Skills Tillbaka till färdigheter
|
||||
@msg tutorial `{id}` has no practice sample självstudien `{id}` har inget övningsexempel
|
||||
@msg open the practice sample of `{id}` first (see learn.openSample) öppna först övningsexemplet för `{id}` (se learn.openSample)
|
||||
@msg finish `{id}` first (see learn.finish) slutför först `{id}` (se learn.finish)
|
||||
@msg `{id}` is the last tutorial in its skill `{id}` är den sista självstudien i sin färdighet
|
||||
|
||||
# Deferred plug-in effects
|
||||
Refresh Deferred Effects Uppdatera uppskjutna effekter
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -7303,6 +7303,19 @@ breakText Glyphs Glifler
|
||||
Show where this tool is Bu aracın nerede olduğunu göster
|
||||
@msg missing `tutorial` (see learn.list) `tutorial` eksik (bkz. learn.list)
|
||||
@msg unknown tutorial `{id}` (see learn.list) bilinmeyen öğretici `{id}` (bkz. learn.list)
|
||||
View more Daha fazlasını gör
|
||||
Finish tutorial Öğreticiyi bitir
|
||||
Next Up Sıradaki
|
||||
You finished the last tutorial in this skill. Bu becerinin son öğreticisini bitirdiniz.
|
||||
Back to skills Becerilere dön
|
||||
Open Tutorial Sample Öğretici Örneğini Aç
|
||||
Finish Tutorial Öğreticiyi Bitir
|
||||
Next Tutorial Sonraki Öğretici
|
||||
Back to Skills Becerilere Dön
|
||||
@msg tutorial `{id}` has no practice sample `{id}` öğreticisinin alıştırma örneği yok
|
||||
@msg open the practice sample of `{id}` first (see learn.openSample) önce `{id}` öğreticisinin alıştırma örneğini açın (bkz. learn.openSample)
|
||||
@msg finish `{id}` first (see learn.finish) önce `{id}` öğreticisini bitirin (bkz. learn.finish)
|
||||
@msg `{id}` is the last tutorial in its skill `{id}`, becerisinin son öğreticisidir
|
||||
|
||||
# Deferred plug-in effects
|
||||
Refresh Deferred Effects Ertelenen Efektleri Yenile
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -6956,6 +6956,19 @@ breakText Glyphs Гліфи
|
||||
Show where this tool is Показати, де розташований цей інструмент
|
||||
@msg missing `tutorial` (see learn.list) відсутній `tutorial` (див. learn.list)
|
||||
@msg unknown tutorial `{id}` (see learn.list) невідомий урок `{id}` (див. learn.list)
|
||||
View more Докладніше
|
||||
Finish tutorial Завершити урок
|
||||
Next Up Далі
|
||||
You finished the last tutorial in this skill. Ви завершили останній урок цієї навички.
|
||||
Back to skills Назад до навичок
|
||||
Open Tutorial Sample Відкрити приклад уроку
|
||||
Finish Tutorial Завершити урок
|
||||
Next Tutorial Наступний урок
|
||||
Back to Skills Назад до навичок
|
||||
@msg tutorial `{id}` has no practice sample урок `{id}` не має навчального прикладу
|
||||
@msg open the practice sample of `{id}` first (see learn.openSample) спочатку відкрийте навчальний приклад уроку `{id}` (див. learn.openSample)
|
||||
@msg finish `{id}` first (see learn.finish) спочатку завершіть `{id}` (див. learn.finish)
|
||||
@msg `{id}` is the last tutorial in its skill `{id}` є останнім уроком своєї навички
|
||||
|
||||
# Deferred plug-in effects
|
||||
Refresh Deferred Effects Оновити відкладені ефекти
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -6957,6 +6957,19 @@ breakText Glyphs Glyph
|
||||
Show where this tool is Hiện vị trí của công cụ này
|
||||
@msg missing `tutorial` (see learn.list) thiếu `tutorial` (xem learn.list)
|
||||
@msg unknown tutorial `{id}` (see learn.list) hướng dẫn không xác định `{id}` (xem learn.list)
|
||||
View more Xem thêm
|
||||
Finish tutorial Hoàn tất hướng dẫn
|
||||
Next Up Tiếp theo
|
||||
You finished the last tutorial in this skill. Bạn đã hoàn tất hướng dẫn cuối cùng của kỹ năng này.
|
||||
Back to skills Quay lại kỹ năng
|
||||
Open Tutorial Sample Mở tệp mẫu của hướng dẫn
|
||||
Finish Tutorial Hoàn tất hướng dẫn
|
||||
Next Tutorial Hướng dẫn tiếp theo
|
||||
Back to Skills Quay lại kỹ năng
|
||||
@msg tutorial `{id}` has no practice sample hướng dẫn `{id}` không có tệp mẫu thực hành
|
||||
@msg open the practice sample of `{id}` first (see learn.openSample) hãy mở tệp mẫu thực hành của `{id}` trước (xem learn.openSample)
|
||||
@msg finish `{id}` first (see learn.finish) hãy hoàn tất `{id}` trước (xem learn.finish)
|
||||
@msg `{id}` is the last tutorial in its skill `{id}` là hướng dẫn cuối cùng trong kỹ năng của nó
|
||||
|
||||
# Deferred plug-in effects
|
||||
Refresh Deferred Effects Làm mới hiệu ứng bị hoãn
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 204 and column 5.
|
@@ -7185,6 +7185,19 @@ breakText Glyphs 字形
|
||||
Show where this tool is 显示此工具的位置
|
||||
@msg missing `tutorial` (see learn.list) 缺少 `tutorial` (请参阅 learn.list)
|
||||
@msg unknown tutorial `{id}` (see learn.list) 未知教程 `{id}` (请参阅 learn.list)
|
||||
View more 查看更多
|
||||
Finish tutorial 完成教程
|
||||
Next Up 下一个
|
||||
You finished the last tutorial in this skill. 你已完成此技能的最后一个教程。
|
||||
Back to skills 返回技能
|
||||
Open Tutorial Sample 打开教程示例
|
||||
Finish Tutorial 完成教程
|
||||
Next Tutorial 下一个教程
|
||||
Back to Skills 返回技能
|
||||
@msg tutorial `{id}` has no practice sample 教程 `{id}` 没有练习示例
|
||||
@msg open the practice sample of `{id}` first (see learn.openSample) 请先打开 `{id}` 的练习示例 (请参阅 learn.openSample)
|
||||
@msg finish `{id}` first (see learn.finish) 请先完成 `{id}` (请参阅 learn.finish)
|
||||
@msg `{id}` is the last tutorial in its skill `{id}` 是其技能的最后一个教程
|
||||
|
||||
# Deferred plug-in effects
|
||||
Refresh Deferred Effects 刷新延迟的效果
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -6991,6 +6991,19 @@ breakText Glyphs 字符
|
||||
Show where this tool is 顯示此工具的位置
|
||||
@msg missing `tutorial` (see learn.list) 未提供 `tutorial` (請參閱 learn.list)
|
||||
@msg unknown tutorial `{id}` (see learn.list) 未知的教學課程 `{id}` (請參閱 learn.list)
|
||||
View more 檢視更多
|
||||
Finish tutorial 完成教學課程
|
||||
Next Up 接下來
|
||||
You finished the last tutorial in this skill. 你已完成此技能的最後一個教學課程。
|
||||
Back to skills 返回技能
|
||||
Open Tutorial Sample 開啟教學課程範例
|
||||
Finish Tutorial 完成教學課程
|
||||
Next Tutorial 下一個教學課程
|
||||
Back to Skills 返回技能
|
||||
@msg tutorial `{id}` has no practice sample 教學課程 `{id}` 沒有練習範例
|
||||
@msg open the practice sample of `{id}` first (see learn.openSample) 請先開啟 `{id}` 的練習範例 (請參閱 learn.openSample)
|
||||
@msg finish `{id}` first (see learn.finish) 請先完成 `{id}` (請參閱 learn.finish)
|
||||
@msg `{id}` is the last tutorial in its skill `{id}` 是其技能的最後一個教學課程
|
||||
|
||||
# Deferred plug-in effects
|
||||
Refresh Deferred Effects 重新整理延後的效果
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -647,6 +647,12 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
|
||||
"",
|
||||
"{} the Learn panel's skills in the order its Skills tab lists them: {skills: [{id, title, tutorials: [{id, title, steps: number of steps}]}]}",
|
||||
),
|
||||
(
|
||||
"learn.skills",
|
||||
"Back to Skills",
|
||||
"",
|
||||
"{} show the Learn panel on its Skills tab with the list of skills, with no skill and no tutorial open, as Back to skills after a skill's last tutorial and the Quick skill guide card do; returns what learn.list returns",
|
||||
),
|
||||
(
|
||||
"learn.show",
|
||||
"Show Tutorial",
|
||||
@@ -659,6 +665,24 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
|
||||
"",
|
||||
"{tool: a tool id, as tool.select takes} mark where that tool is for 3 seconds without choosing it, as the link on a tool's name in a Learn tutorial step does: its toolbar button, a row of its collapsed group's flyout (which opens), its group's torn-off strip (which comes to the front), or, when no shown toolbar holds it, its row in the All Tools drawer (which opens); a Tools panel hidden with Window › Tools shows again; Escape or the next locate removes the mark; a missing, non-string or unknown id is an error and changes nothing; returns {tool, location: toolbar|flyout|strip|allTools|unavailable, toolbar: the name of the toolbar made with New Toolbar that holds it, or null}, with reason when Presentation Mode or Tab hides the toolbars",
|
||||
),
|
||||
(
|
||||
"learn.openSample",
|
||||
"Open Tutorial Sample",
|
||||
"",
|
||||
"{tutorial: id (see learn.list)} open that tutorial's practice sample as a new untitled document, which becomes the active one, and move the Learn view to the tutorial, as its View more button does; each call opens another copy and open documents stay as they are; a missing, non-string or unknown id is an error and changes nothing; returns {tutorial, index, title} of the new document",
|
||||
),
|
||||
(
|
||||
"learn.finish",
|
||||
"Finish Tutorial",
|
||||
"",
|
||||
"{tutorial: id (see learn.list)} mark a tutorial whose sample learn.openSample opened as finished, as its Finish tutorial button does; finishing it again keeps one entry; a missing, non-string or unknown id, or a tutorial whose sample was not opened, is an error and changes nothing; returns {tutorial, finished: true, next: id of the next tutorial in its skill, or null after the skill's last one}",
|
||||
),
|
||||
(
|
||||
"learn.next",
|
||||
"Next Tutorial",
|
||||
"",
|
||||
"{tutorial: id (see learn.list)} show the Learn panel on the tutorial after a finished one in the same skill, as its Next Up button does, without opening a sample; a missing, non-string or unknown id, a tutorial not finished, or a skill's last tutorial is an error and changes nothing; returns {skill, tutorial, title} of the next tutorial",
|
||||
),
|
||||
("window.brightness", "UI Brightness", "", "{brightness: dark|mediumDark|mediumLight|light}"),
|
||||
(
|
||||
"window.workspace",
|
||||
|
||||
@@ -3,11 +3,16 @@
|
||||
//! section with the Quick skill guide card, which opens the Skills tab. The Skills tab lists each
|
||||
//! skill with an empty progress ring and its count of tutorials; a skill lists its tutorials, and a
|
||||
//! tutorial previews its numbered steps. A tool's name in a step is a link that shows where the tool
|
||||
//! is ([`locate`]). Back goes up one level. `learn.list` lists the tutorials, `learn.show` opens one,
|
||||
//! `learn.locateTool` shows where a tool is and `window.learn` shows or hides the panel.
|
||||
//! is ([`locate`]). Back goes up one level. Under the steps, View more opens the tutorial's practice
|
||||
//! sample as a new untitled document; Finish tutorial then marks it finished, and Next Up shows the
|
||||
//! next tutorial in the same skill. `learn.list` lists the tutorials, `learn.skills` shows the list
|
||||
//! of skills, `learn.show` opens one tutorial, `learn.locateTool` shows where a tool is,
|
||||
//! `learn.openSample`, `learn.finish` and `learn.next` run the three practice controls, and
|
||||
//! `window.learn` shows or hides the panel.
|
||||
|
||||
pub mod catalog;
|
||||
pub mod locate;
|
||||
pub mod samples;
|
||||
|
||||
use egui::{Sense, Stroke, Ui, vec2};
|
||||
use serde_json::{Value, json};
|
||||
@@ -35,6 +40,14 @@ pub struct View {
|
||||
pub tutorial: Option<&'static str>,
|
||||
}
|
||||
|
||||
/// The tutorials whose samples View more opened, and the tutorials finished, in the order they
|
||||
/// were finished. Each id is listed once.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct Practice {
|
||||
pub opened: Vec<&'static str>,
|
||||
pub finished: Vec<&'static str>,
|
||||
}
|
||||
|
||||
pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
ui.horizontal(|ui| {
|
||||
for (tab, label) in [(Tab::ForYou, tl!("For you")), (Tab::Skills, tl!("Skills"))] {
|
||||
@@ -75,8 +88,10 @@ fn for_you(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
});
|
||||
let resp = ui.interact(card.response.rect, ui.scope_id().with("quick-skill-guide"), Sense::click());
|
||||
resp.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, ui.is_enabled(), tl!("Quick skill guide")));
|
||||
if resp.clicked() {
|
||||
app.ui.learn = View { tab: Tab::Skills, skill: None, tutorial: None };
|
||||
if resp.clicked()
|
||||
&& let Err(e) = app.run("learn.skills", json!({}))
|
||||
{
|
||||
app.status(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +104,7 @@ fn skills(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
app.ui.learn.tutorial = None;
|
||||
}
|
||||
steps(app, ui, tutorial);
|
||||
practice(app, ui, tutorial);
|
||||
return;
|
||||
}
|
||||
if let Some(skill) = app.ui.learn.skill.and_then(catalog::skill) {
|
||||
@@ -163,6 +179,50 @@ fn tool_link(ui: &mut Ui, t: &Tokens, name: &str) -> egui::Response {
|
||||
resp.on_hover_cursor(egui::CursorIcon::PointingHand).on_hover_text(tl!("Show where this tool is"))
|
||||
}
|
||||
|
||||
/// The controls under a tutorial's steps: View more; Finish tutorial once its sample is open; and
|
||||
/// once it is finished, Next Up with the next tutorial's title, or for the skill's last tutorial a
|
||||
/// line saying so and Back to skills.
|
||||
fn practice(app: &mut VectorcraftApp, ui: &mut Ui, tutorial: &'static catalog::Tutorial) {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let id = tutorial.id;
|
||||
let params = json!({ "tutorial": id });
|
||||
let mut run = None;
|
||||
ui.add_space(6.0);
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
if ui.button(tl!("View more")).clicked() {
|
||||
run = Some(("learn.openSample", params.clone()));
|
||||
}
|
||||
let done = &app.ui.learn_practice;
|
||||
if done.opened.contains(&id) && !done.finished.contains(&id) && ui.button(tl!("Finish tutorial")).clicked() {
|
||||
run = Some(("learn.finish", params.clone()));
|
||||
}
|
||||
});
|
||||
if app.ui.learn_practice.finished.contains(&id) {
|
||||
ui.add_space(6.0);
|
||||
match catalog::next(id) {
|
||||
Some(next) => {
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
if ui.button(tl!("Next Up")).clicked() {
|
||||
run = Some(("learn.next", params.clone()));
|
||||
}
|
||||
ui.add(egui::Label::new(egui::RichText::new(next.title).color(t.text)).wrap());
|
||||
});
|
||||
}
|
||||
None => {
|
||||
ui.add(egui::Label::new(egui::RichText::new(tl!("You finished the last tutorial in this skill.")).color(t.text_dim)).wrap());
|
||||
if ui.button(tl!("Back to skills")).clicked() {
|
||||
run = Some(("learn.skills", json!({})));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some((cmd, params)) = run
|
||||
&& let Err(e) = app.run(cmd, params)
|
||||
{
|
||||
app.status(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// A clickable row: `title`, and for a skill (`tutorials`) a progress ring before it and the
|
||||
/// count of tutorials done of all after it. No progress is kept yet, so the ring is empty.
|
||||
fn row(ui: &mut Ui, title: &str, tutorials: Option<usize>) -> egui::Response {
|
||||
@@ -191,13 +251,18 @@ fn row(ui: &mut Ui, title: &str, tutorials: Option<usize>) -> egui::Response {
|
||||
resp.on_hover_cursor(egui::CursorIcon::PointingHand)
|
||||
}
|
||||
|
||||
/// The Learn panel's UI commands: `window.learn`, `learn.list`, `learn.show` and `learn.locateTool`.
|
||||
/// The Learn panel's UI commands: `window.learn`, `learn.list`, `learn.skills`, `learn.show`,
|
||||
/// `learn.locateTool`, `learn.openSample`, `learn.finish` and `learn.next`.
|
||||
pub(crate) fn command(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option<Result<Value, String>> {
|
||||
Some(match id {
|
||||
"window.learn" => toggle(app, p),
|
||||
"learn.list" => Ok(list()),
|
||||
"learn.skills" => skills_list(app),
|
||||
"learn.show" => open(app, p),
|
||||
"learn.locateTool" => locate::locate_tool(app, p),
|
||||
"learn.openSample" => open_sample(app, p),
|
||||
"learn.finish" => finish(app, p),
|
||||
"learn.next" => next(app, p),
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
@@ -231,21 +296,84 @@ fn list() -> Value {
|
||||
json!({ "skills": skills.collect::<Vec<_>>() })
|
||||
}
|
||||
|
||||
/// `learn.skills`: Back to skills, and the Quick skill guide card. Show the panel on the Skills tab
|
||||
/// with the list of skills, with no skill and no tutorial open → what `learn.list` answers.
|
||||
fn skills_list(app: &mut VectorcraftApp) -> Result<Value, String> {
|
||||
crate::panel_docking::reveal_panel(app, ID)?;
|
||||
app.ui.learn = View { tab: Tab::Skills, skill: None, tutorial: None };
|
||||
Ok(list())
|
||||
}
|
||||
|
||||
/// `learn.show {tutorial}`: show the panel on the Skills tab with that tutorial's steps → the
|
||||
/// tutorial. A missing or unknown id is an error and changes nothing.
|
||||
fn open(app: &mut VectorcraftApp, p: &Value) -> Result<Value, String> {
|
||||
let key = "tutorial";
|
||||
let id = match p.get(key) {
|
||||
None | Some(Value::Null) => return Err("missing `tutorial` (see learn.list)".into()),
|
||||
Some(Value::String(id)) => id.as_str(),
|
||||
Some(_) => return Err(format!("`{key}` must be a string")),
|
||||
};
|
||||
let (skill, tutorial) = catalog::tutorial(id).ok_or_else(|| format!("unknown tutorial `{id}` (see learn.list)"))?;
|
||||
let (skill, tutorial) = tutorial_param(p)?;
|
||||
crate::panel_docking::reveal_panel(app, ID)?;
|
||||
app.ui.learn = View { tab: Tab::Skills, skill: Some(skill.id), tutorial: Some(tutorial.id) };
|
||||
let steps: Vec<String> = tutorial.steps.iter().map(catalog::Step::plain).collect();
|
||||
Ok(json!({"skill": skill.id, "tutorial": tutorial.id, "title": tutorial.title, "steps": steps}))
|
||||
}
|
||||
|
||||
/// The tutorial `p`'s `tutorial` names, with its skill. A missing, non-string or unknown id is an
|
||||
/// error.
|
||||
fn tutorial_param(p: &Value) -> Result<(&'static catalog::Skill, &'static catalog::Tutorial), String> {
|
||||
let key = "tutorial";
|
||||
let id = match p.get(key) {
|
||||
None | Some(Value::Null) => return Err("missing `tutorial` (see learn.list)".into()),
|
||||
Some(Value::String(id)) => id.as_str(),
|
||||
Some(_) => return Err(format!("`{key}` must be a string")),
|
||||
};
|
||||
catalog::tutorial(id).ok_or_else(|| format!("unknown tutorial `{id}` (see learn.list)"))
|
||||
}
|
||||
|
||||
/// `learn.openSample {tutorial}`: View more. Open that tutorial's practice sample as a new untitled
|
||||
/// document, which becomes the active one, and move the Learn view to the tutorial → `{tutorial,
|
||||
/// index, title}` of the new document. Each call opens another copy; open documents stay as they
|
||||
/// are. An id that is not valid, or a sample that does not read, is an error and changes nothing.
|
||||
fn open_sample(app: &mut VectorcraftApp, p: &Value) -> Result<Value, String> {
|
||||
let (skill, tutorial) = tutorial_param(p)?;
|
||||
let id = tutorial.id;
|
||||
let svg = samples::sample(id).ok_or_else(|| format!("tutorial `{id}` has no practice sample"))?;
|
||||
let name = format!("{id}.svg");
|
||||
let opened = vectorcraft_engine::cmd::fileio::open_template(&mut app.session, &name, svg.as_bytes(), None).map_err(|e| e.to_string())?;
|
||||
app.sync_views();
|
||||
if !app.ui.learn_practice.opened.contains(&id) {
|
||||
app.ui.learn_practice.opened.push(id);
|
||||
}
|
||||
app.ui.learn = View { tab: Tab::Skills, skill: Some(skill.id), tutorial: Some(id) };
|
||||
Ok(json!({"tutorial": id, "index": opened["index"], "title": opened["title"]}))
|
||||
}
|
||||
|
||||
/// `learn.finish {tutorial}`: Finish tutorial. Mark a tutorial whose sample was opened finished →
|
||||
/// `{tutorial, finished: true, next}`, where `next` is the id of the next tutorial in its skill, or
|
||||
/// null after the skill's last one. Finishing it again keeps one entry. The Learn view stays as it is.
|
||||
fn finish(app: &mut VectorcraftApp, p: &Value) -> Result<Value, String> {
|
||||
let (_, tutorial) = tutorial_param(p)?;
|
||||
let id = tutorial.id;
|
||||
let practice = &mut app.ui.learn_practice;
|
||||
if !practice.opened.contains(&id) {
|
||||
return Err(format!("open the practice sample of `{id}` first (see learn.openSample)"));
|
||||
}
|
||||
if !practice.finished.contains(&id) {
|
||||
practice.finished.push(id);
|
||||
}
|
||||
Ok(json!({"tutorial": id, "finished": true, "next": catalog::next(id).map(|t| t.id)}))
|
||||
}
|
||||
|
||||
/// `learn.next {tutorial}`: Next Up. Show the panel on the tutorial after a finished one in the
|
||||
/// same skill → `{skill, tutorial, title}` of that tutorial. It opens no sample. An unfinished
|
||||
/// tutorial, or a skill's last tutorial, is an error and changes nothing.
|
||||
fn next(app: &mut VectorcraftApp, p: &Value) -> Result<Value, String> {
|
||||
let (skill, tutorial) = tutorial_param(p)?;
|
||||
let id = tutorial.id;
|
||||
if !app.ui.learn_practice.finished.contains(&id) {
|
||||
return Err(format!("finish `{id}` first (see learn.finish)"));
|
||||
}
|
||||
let after = catalog::next(id).ok_or_else(|| format!("`{id}` is the last tutorial in its skill"))?;
|
||||
crate::panel_docking::reveal_panel(app, ID)?;
|
||||
app.ui.learn = View { tab: Tab::Skills, skill: Some(skill.id), tutorial: Some(after.id) };
|
||||
Ok(json!({"skill": skill.id, "tutorial": after.id, "title": after.title}))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
@@ -201,3 +201,10 @@ pub fn skill(id: &str) -> Option<&'static Skill> {
|
||||
pub fn tutorial(id: &str) -> Option<(&'static Skill, &'static Tutorial)> {
|
||||
SKILLS.iter().find_map(|s| s.tutorials.iter().find(|t| t.id == id).map(|t| (s, t)))
|
||||
}
|
||||
|
||||
/// The tutorial after the one whose id is `id` in the same skill; none for a skill's last tutorial.
|
||||
pub fn next(id: &str) -> Option<&'static Tutorial> {
|
||||
let (skill, _) = tutorial(id)?;
|
||||
let at = skill.tutorials.iter().position(|t| t.id == id)?;
|
||||
skill.tutorials.get(at + 1)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
//! The practice samples that a tutorial's View more opens: one small original SVG drawing for each
|
||||
//! tutorial, with only the art its steps work on. Each one opens as a new untitled document.
|
||||
|
||||
/// The largest sample, in bytes: the catalog test keeps every sample under it.
|
||||
pub const MAX_SAMPLE_BYTES: usize = 16 * 1024;
|
||||
|
||||
/// Every tutorial's sample, by tutorial id, in the catalog's order.
|
||||
pub const SAMPLES: &[(&str, &str)] = &[
|
||||
(
|
||||
"draw-and-color",
|
||||
r##"<svg xmlns="http://www.w3.org/2000/svg" width="400" height="300" viewBox="0 0 400 300">
|
||||
<rect x="100" y="75" width="200" height="150" fill="none" stroke="#9aa0a6" stroke-width="2" stroke-dasharray="8 6"/>
|
||||
</svg>
|
||||
"##,
|
||||
),
|
||||
(
|
||||
"combine-shapes",
|
||||
r##"<svg xmlns="http://www.w3.org/2000/svg" width="400" height="300" viewBox="0 0 400 300">
|
||||
<circle cx="160" cy="150" r="80" fill="#4c8bf5"/>
|
||||
<circle cx="250" cy="150" r="80" fill="#f4b400"/>
|
||||
</svg>
|
||||
"##,
|
||||
),
|
||||
(
|
||||
"add-headline",
|
||||
r##"<svg xmlns="http://www.w3.org/2000/svg" width="400" height="300" viewBox="0 0 400 300">
|
||||
<rect x="40" y="90" width="320" height="120" rx="12" fill="#e8f0fe"/>
|
||||
</svg>
|
||||
"##,
|
||||
),
|
||||
(
|
||||
"pen-paths",
|
||||
r##"<svg xmlns="http://www.w3.org/2000/svg" width="400" height="300" viewBox="0 0 400 300">
|
||||
<path d="M 200 60 L 320 240 L 80 240 Z" fill="none" stroke="#9aa0a6" stroke-width="2" stroke-dasharray="8 6"/>
|
||||
<circle cx="200" cy="60" r="5" fill="#1a73e8"/>
|
||||
<circle cx="320" cy="240" r="5" fill="#1a73e8"/>
|
||||
<circle cx="80" cy="240" r="5" fill="#1a73e8"/>
|
||||
</svg>
|
||||
"##,
|
||||
),
|
||||
(
|
||||
"smooth-curves",
|
||||
r##"<svg xmlns="http://www.w3.org/2000/svg" width="400" height="300" viewBox="0 0 400 300">
|
||||
<path d="M 40 150 C 100 50 160 50 200 150 C 240 250 300 250 360 150" fill="none" stroke="#9aa0a6" stroke-width="2" stroke-dasharray="8 6"/>
|
||||
</svg>
|
||||
"##,
|
||||
),
|
||||
(
|
||||
"pencil-sketch",
|
||||
r##"<svg xmlns="http://www.w3.org/2000/svg" width="400" height="300" viewBox="0 0 400 300">
|
||||
<path d="M 60 200 C 100 80 180 80 200 150 C 220 220 300 220 340 100" fill="none" stroke="#9aa0a6" stroke-width="2" stroke-dasharray="8 6"/>
|
||||
</svg>
|
||||
"##,
|
||||
),
|
||||
(
|
||||
"fill-and-stroke",
|
||||
r##"<svg xmlns="http://www.w3.org/2000/svg" width="400" height="300" viewBox="0 0 400 300">
|
||||
<rect x="110" y="80" width="180" height="140" rx="16" fill="#f4b400" stroke="#1a73e8" stroke-width="6"/>
|
||||
</svg>
|
||||
"##,
|
||||
),
|
||||
(
|
||||
"gradient",
|
||||
r##"<svg xmlns="http://www.w3.org/2000/svg" width="400" height="300" viewBox="0 0 400 300">
|
||||
<circle cx="200" cy="150" r="100" fill="#34a853"/>
|
||||
</svg>
|
||||
"##,
|
||||
),
|
||||
(
|
||||
"copy-colors",
|
||||
r##"<svg xmlns="http://www.w3.org/2000/svg" width="400" height="300" viewBox="0 0 400 300">
|
||||
<rect x="50" y="90" width="120" height="120" fill="#db4437" stroke="#202124" stroke-width="4"/>
|
||||
<circle cx="290" cy="150" r="60" fill="#dadce0"/>
|
||||
</svg>
|
||||
"##,
|
||||
),
|
||||
(
|
||||
"area-type",
|
||||
r##"<svg xmlns="http://www.w3.org/2000/svg" width="400" height="300" viewBox="0 0 400 300">
|
||||
<rect x="80" y="60" width="240" height="180" fill="none" stroke="#1a73e8" stroke-width="2"/>
|
||||
</svg>
|
||||
"##,
|
||||
),
|
||||
(
|
||||
"type-on-path",
|
||||
r##"<svg xmlns="http://www.w3.org/2000/svg" width="400" height="300" viewBox="0 0 400 300">
|
||||
<path d="M 50 200 C 120 80 280 80 350 200" fill="none" stroke="#1a73e8" stroke-width="2"/>
|
||||
</svg>
|
||||
"##,
|
||||
),
|
||||
(
|
||||
"outline-type",
|
||||
r##"<svg xmlns="http://www.w3.org/2000/svg" width="400" height="300" viewBox="0 0 400 300">
|
||||
<text x="60" y="170" font-size="64" fill="#202124">Outline</text>
|
||||
</svg>
|
||||
"##,
|
||||
),
|
||||
];
|
||||
|
||||
/// The sample of the tutorial whose id is `id`.
|
||||
pub fn sample(id: &str) -> Option<&'static str> {
|
||||
SAMPLES.iter().find(|(tutorial, _)| *tutorial == id).map(|(_, svg)| *svg)
|
||||
}
|
||||
@@ -4,7 +4,7 @@ use egui::accesskit::Node;
|
||||
use serde_json::json;
|
||||
use vectorcraft_engine::Session;
|
||||
|
||||
use super::{ID, Tab, View, catalog, show};
|
||||
use super::{ID, Practice, Tab, View, catalog, samples, show};
|
||||
use crate::VectorcraftApp;
|
||||
use crate::tests_labels::painted_text;
|
||||
|
||||
@@ -228,6 +228,255 @@ fn learn_show_opens_a_tutorial_and_learn_list_lists_them() {
|
||||
assert_eq!(skills[2]["tutorials"][1], json!({"id": "gradient", "title": "Add a gradient", "steps": 3}));
|
||||
}
|
||||
|
||||
/// Every tutorial has one sample of its own, in the catalog's order, each one small and read
|
||||
/// without warnings into art of its own.
|
||||
#[test]
|
||||
fn every_tutorial_has_its_own_readable_sample() {
|
||||
let ids: Vec<&str> = catalog::SKILLS.iter().flat_map(|s| s.tutorials.iter().map(|t| t.id)).collect();
|
||||
let sampled: Vec<&str> = samples::SAMPLES.iter().map(|(id, _)| *id).collect();
|
||||
assert_eq!(sampled, ids, "one sample for each tutorial, in the catalog's order");
|
||||
let mut texts = HashSet::new();
|
||||
for (id, svg) in samples::SAMPLES {
|
||||
assert!(svg.len() <= samples::MAX_SAMPLE_BYTES, "{id}: {} bytes", svg.len());
|
||||
assert!(texts.insert(*svg), "{id} reuses another tutorial's sample");
|
||||
let loaded = vectorcraft_engine::cmd::fileio::load(&format!("{id}.svg"), svg.as_bytes()).unwrap_or_else(|e| panic!("{id}: {e}"));
|
||||
assert!(loaded.warnings.is_empty(), "{id}: {:?}", loaded.warnings);
|
||||
let mut art = 0;
|
||||
loaded.doc.walk(|n| art += usize::from(!n.is_layer()));
|
||||
assert!(art > 0, "{id} has no art");
|
||||
}
|
||||
}
|
||||
|
||||
/// Next Up stays in the tutorial's skill: a skill's last tutorial, the catalog's last one
|
||||
/// included, has no next tutorial.
|
||||
#[test]
|
||||
fn the_next_tutorial_is_the_next_one_in_the_same_skill() {
|
||||
for skill in catalog::SKILLS {
|
||||
for pair in skill.tutorials.windows(2) {
|
||||
assert_eq!(catalog::next(pair[0].id).map(|t| t.id), Some(pair[1].id));
|
||||
}
|
||||
let last = skill.tutorials.last().unwrap();
|
||||
assert!(catalog::next(last.id).is_none(), "{} is the last of {}", last.id, skill.id);
|
||||
}
|
||||
assert!(catalog::next("nope").is_none());
|
||||
}
|
||||
|
||||
/// View more opens the tutorial's sample as a new untitled document beside a modified one, which
|
||||
/// stays as it was; another tutorial opens another sample, and View more again opens another copy.
|
||||
#[test]
|
||||
fn view_more_opens_the_tutorial_s_sample_as_a_new_untitled_document() {
|
||||
let mut app = app();
|
||||
let ctx = context();
|
||||
app.run("file.new", json!({"width": 400, "height": 300})).unwrap();
|
||||
app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 30, "height": 20})).unwrap();
|
||||
let work = app.session.documents()[0].doc.node_count();
|
||||
let (skill, tutorial) = catalog::tutorial("pen-paths").unwrap();
|
||||
app.ui.learn = View { tab: Tab::Skills, skill: Some(skill.id), tutorial: Some(tutorial.id) };
|
||||
click(&mut app, &ctx, "View more", show);
|
||||
let docs = app.session.documents();
|
||||
assert_eq!(docs.len(), 2, "{}", app.ui.status);
|
||||
assert_eq!(app.session.active_index(), Some(1));
|
||||
assert_eq!(docs[0].doc.node_count(), work, "the open document stays as it was");
|
||||
assert!(docs[1].path.is_none() && docs[1].title().starts_with("Untitled-"), "{}", docs[1].title());
|
||||
let pen = docs[1].doc.node_count();
|
||||
assert_eq!(app.ui.learn_practice, Practice { opened: vec!["pen-paths"], finished: vec![] });
|
||||
|
||||
let r = app.run("learn.openSample", json!({"tutorial": "gradient"})).unwrap();
|
||||
assert_eq!(r["tutorial"], "gradient");
|
||||
assert_eq!(r["index"], 2);
|
||||
assert_eq!(r["title"], json!(app.session.documents()[2].title()));
|
||||
assert_ne!(app.session.documents()[2].doc.node_count(), pen, "another tutorial, another sample");
|
||||
assert_eq!(app.ui.learn, View { tab: Tab::Skills, skill: Some("color"), tutorial: Some("gradient") });
|
||||
app.run("learn.openSample", json!({"tutorial": "gradient"})).unwrap();
|
||||
assert_eq!(app.session.documents().len(), 4, "View more again opens another copy");
|
||||
assert_eq!(app.ui.learn_practice.opened, ["pen-paths", "gradient"]);
|
||||
}
|
||||
|
||||
/// Finish tutorial shows once the sample is open; finishing shows Next Up with the next
|
||||
/// tutorial's title, and Next Up shows that tutorial without opening its sample.
|
||||
#[test]
|
||||
fn finishing_a_tutorial_offers_next_up_in_its_skill() {
|
||||
let mut app = app();
|
||||
let ctx = context();
|
||||
let (skill, tutorial) = catalog::tutorial("pen-paths").unwrap();
|
||||
app.ui.learn = View { tab: Tab::Skills, skill: Some(skill.id), tutorial: Some(tutorial.id) };
|
||||
let text = painted_text(&mut app, show);
|
||||
assert!(text.contains("View more") && !text.contains("Finish tutorial") && !text.contains("Next Up"), "{text}");
|
||||
click(&mut app, &ctx, "View more", show);
|
||||
let text = painted_text(&mut app, show);
|
||||
assert!(text.contains("Finish tutorial") && !text.contains("Next Up"), "{text}");
|
||||
click(&mut app, &ctx, "Finish tutorial", show);
|
||||
assert_eq!(app.ui.learn_practice.finished, ["pen-paths"]);
|
||||
let text = painted_text(&mut app, show);
|
||||
assert!(text.contains("Next Up") && text.contains("Draw smooth curves") && !text.contains("Finish tutorial"), "{text}");
|
||||
let docs = app.session.documents().len();
|
||||
click(&mut app, &ctx, "Next Up", show);
|
||||
assert_eq!(app.ui.learn, View { tab: Tab::Skills, skill: Some("draw"), tutorial: Some("smooth-curves") });
|
||||
assert_eq!(app.session.documents().len(), docs, "Next Up opens no sample");
|
||||
let text = painted_text(&mut app, show);
|
||||
assert!(text.contains("View more") && !text.contains("Finish tutorial") && !text.contains("Next Up"), "{text}");
|
||||
}
|
||||
|
||||
/// One press and release of `key` in what `draw` draws.
|
||||
fn key(app: &mut VectorcraftApp, ctx: &egui::Context, key: egui::Key, draw: fn(&mut VectorcraftApp, &mut egui::Ui)) {
|
||||
let event = |pressed| egui::Event::Key { key, physical_key: None, pressed, repeat: false, modifiers: Default::default() };
|
||||
frame(app, ctx, vec![event(true)], draw);
|
||||
frame(app, ctx, vec![event(false)], draw);
|
||||
}
|
||||
|
||||
/// Press Tab until the control labeled `name` has the keyboard focus.
|
||||
fn tab_to(app: &mut VectorcraftApp, ctx: &egui::Context, name: &str, draw: fn(&mut VectorcraftApp, &mut egui::Ui)) {
|
||||
for _ in 0..32 {
|
||||
key(app, ctx, egui::Key::Tab, draw);
|
||||
let at = center(&frame(app, ctx, vec![], draw), name).unwrap_or_else(|| panic!("{name} is drawn"));
|
||||
if ctx.memory(|m| m.focused()).and_then(|id| ctx.read_response(id)).is_some_and(|r| r.rect.contains(at)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
panic!("Tab did not reach {name}");
|
||||
}
|
||||
|
||||
/// View more, Finish tutorial and Next Up are reached with Tab and pressed with Enter.
|
||||
#[test]
|
||||
fn the_practice_controls_work_from_the_keyboard() {
|
||||
let mut app = app();
|
||||
let ctx = context();
|
||||
let (skill, tutorial) = catalog::tutorial("pen-paths").unwrap();
|
||||
app.ui.learn = View { tab: Tab::Skills, skill: Some(skill.id), tutorial: Some(tutorial.id) };
|
||||
frame(&mut app, &ctx, vec![], show);
|
||||
tab_to(&mut app, &ctx, "View more", show);
|
||||
key(&mut app, &ctx, egui::Key::Enter, show);
|
||||
assert_eq!(app.session.documents().len(), 1, "{}", app.ui.status);
|
||||
assert_eq!(app.ui.learn_practice.opened, ["pen-paths"]);
|
||||
tab_to(&mut app, &ctx, "Finish tutorial", show);
|
||||
key(&mut app, &ctx, egui::Key::Enter, show);
|
||||
assert_eq!(app.ui.learn_practice.finished, ["pen-paths"]);
|
||||
tab_to(&mut app, &ctx, "Next Up", show);
|
||||
key(&mut app, &ctx, egui::Key::Enter, show);
|
||||
assert_eq!(app.ui.learn, View { tab: Tab::Skills, skill: Some("draw"), tutorial: Some("smooth-curves") });
|
||||
assert_eq!(app.session.documents().len(), 1, "Next Up opens no sample");
|
||||
}
|
||||
|
||||
/// The panel 150 points wide.
|
||||
fn narrow(app: &mut VectorcraftApp, ui: &mut egui::Ui) {
|
||||
ui.allocate_ui(egui::vec2(150.0, 800.0), |ui| {
|
||||
ui.set_max_width(150.0);
|
||||
show(app, ui);
|
||||
});
|
||||
}
|
||||
|
||||
/// In a narrow panel the practice controls wrap inside it.
|
||||
#[test]
|
||||
fn the_practice_controls_fit_a_narrow_panel() {
|
||||
let mut app = app();
|
||||
let ctx = context();
|
||||
app.run("learn.openSample", json!({"tutorial": "pen-paths"})).unwrap();
|
||||
frame(&mut app, &ctx, vec![], narrow);
|
||||
let nodes = frame(&mut app, &ctx, vec![], narrow);
|
||||
for name in ["View more", "Finish tutorial"] {
|
||||
let node = nodes.iter().find(|n| n.label() == Some(name)).unwrap_or_else(|| panic!("{name} is drawn"));
|
||||
let b = node.bounds().unwrap();
|
||||
assert!(b.x1 <= 151.0, "{name} ends at {}", b.x1);
|
||||
}
|
||||
}
|
||||
|
||||
/// A skill's last tutorial, finished, says so and offers Back to skills instead of Next Up, even
|
||||
/// when another skill follows; `learn.next` there is an error.
|
||||
#[test]
|
||||
fn the_last_tutorial_of_a_skill_offers_back_to_skills() {
|
||||
let mut app = app();
|
||||
let ctx = context();
|
||||
for id in ["add-headline", "outline-type"] {
|
||||
app.run("learn.openSample", json!({"tutorial": id})).unwrap();
|
||||
assert_eq!(app.run("learn.finish", json!({"tutorial": id})).unwrap(), json!({"tutorial": id, "finished": true, "next": null}));
|
||||
let text = painted_text(&mut app, show);
|
||||
assert!(text.contains("You finished the last tutorial in this skill.") && !text.contains("Next Up"), "{text}");
|
||||
let before = app.ui.learn.clone();
|
||||
assert_eq!(app.run("learn.next", json!({"tutorial": id})).unwrap_err(), format!("`{id}` is the last tutorial in its skill"));
|
||||
assert_eq!(app.ui.learn, before);
|
||||
click(&mut app, &ctx, "Back to skills", show);
|
||||
assert_eq!(app.ui.learn, View { tab: Tab::Skills, skill: None, tutorial: None });
|
||||
}
|
||||
}
|
||||
|
||||
/// `learn.finish` needs the sample opened, and finishing again keeps one entry; `learn.next` needs
|
||||
/// the tutorial finished and answers the next tutorial.
|
||||
#[test]
|
||||
fn learn_finish_and_learn_next_follow_the_practice() {
|
||||
let mut app = app();
|
||||
let open = "open the practice sample of `gradient` first (see learn.openSample)";
|
||||
assert_eq!(app.run("learn.finish", json!({"tutorial": "gradient"})).unwrap_err(), open);
|
||||
assert_eq!(app.run("learn.next", json!({"tutorial": "gradient"})).unwrap_err(), "finish `gradient` first (see learn.finish)");
|
||||
assert_eq!(app.ui.learn_practice, Practice::default());
|
||||
app.run("learn.openSample", json!({"tutorial": "gradient"})).unwrap();
|
||||
assert_eq!(app.run("learn.next", json!({"tutorial": "gradient"})).unwrap_err(), "finish `gradient` first (see learn.finish)");
|
||||
for _ in 0..2 {
|
||||
let r = app.run("learn.finish", json!({"tutorial": "gradient"})).unwrap();
|
||||
assert_eq!(r, json!({"tutorial": "gradient", "finished": true, "next": "copy-colors"}));
|
||||
}
|
||||
assert_eq!(app.ui.learn_practice.finished, ["gradient"], "finishing again keeps one entry");
|
||||
let r = app.run("learn.next", json!({"tutorial": "gradient"})).unwrap();
|
||||
assert_eq!(r, json!({"skill": "color", "tutorial": "copy-colors", "title": "Copy colors from other art"}));
|
||||
assert!(shown(&app));
|
||||
assert_eq!(app.ui.learn, View { tab: Tab::Skills, skill: Some("color"), tutorial: Some("copy-colors") });
|
||||
}
|
||||
|
||||
/// `learn.skills` shows the panel on the Skills tab with the list of skills and answers what
|
||||
/// `learn.list` answers. Back to skills and the Quick skill guide card run it: each one also shows
|
||||
/// a panel that was hidden, as the command does.
|
||||
#[test]
|
||||
fn learn_skills_shows_the_list_of_skills() {
|
||||
let skills = View { tab: Tab::Skills, skill: None, tutorial: None };
|
||||
assert!(crate::menus::UI_COMMANDS.iter().any(|c| c.0 == "learn.skills"), "learn.skills is in the UI command catalog");
|
||||
let mut app = app();
|
||||
app.run("learn.show", json!({"tutorial": "gradient"})).unwrap();
|
||||
app.run("window.learn", json!({"show": false})).unwrap();
|
||||
assert!(!shown(&app));
|
||||
let list = app.run("learn.list", json!({})).unwrap();
|
||||
assert_eq!(app.run("learn.skills", json!({})).unwrap(), list);
|
||||
assert_eq!(app.ui.learn, skills);
|
||||
assert!(shown(&app));
|
||||
|
||||
// Back to skills, after a skill's last tutorial.
|
||||
let mut app = self::app();
|
||||
let ctx = context();
|
||||
app.run("learn.openSample", json!({"tutorial": "outline-type"})).unwrap();
|
||||
app.run("learn.finish", json!({"tutorial": "outline-type"})).unwrap();
|
||||
assert!(!shown(&app));
|
||||
click(&mut app, &ctx, "Back to skills", show);
|
||||
assert_eq!(app.ui.learn, skills);
|
||||
assert!(shown(&app), "Back to skills runs learn.skills");
|
||||
|
||||
// The Quick skill guide card on the For you tab.
|
||||
let mut app = self::app();
|
||||
let ctx = context();
|
||||
assert!(!shown(&app));
|
||||
click(&mut app, &ctx, "Quick skill guide", show);
|
||||
assert_eq!(app.ui.learn, skills);
|
||||
assert!(shown(&app), "the Quick skill guide card runs learn.skills");
|
||||
}
|
||||
|
||||
/// The practice commands reject a missing, mistyped or unknown tutorial id and change nothing.
|
||||
#[test]
|
||||
fn the_practice_commands_reject_a_missing_or_unknown_tutorial() {
|
||||
let mut app = app();
|
||||
app.run("file.new", json!({"width": 400, "height": 300})).unwrap();
|
||||
for cmd in ["learn.openSample", "learn.finish", "learn.next"] {
|
||||
for (params, error) in [
|
||||
(json!({}), "missing `tutorial` (see learn.list)"),
|
||||
(json!({"tutorial": null}), "missing `tutorial` (see learn.list)"),
|
||||
(json!({"tutorial": 3}), "`tutorial` must be a string"),
|
||||
(json!({"tutorial": "nope"}), "unknown tutorial `nope` (see learn.list)"),
|
||||
] {
|
||||
assert_eq!(app.run(cmd, params).unwrap_err(), error, "{cmd}");
|
||||
assert_eq!(app.ui.learn, View::default());
|
||||
assert_eq!(app.ui.learn_practice, Practice::default());
|
||||
assert_eq!(app.session.documents().len(), 1);
|
||||
assert!(!shown(&app));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A missing, mistyped or unknown tutorial id is an error and leaves the panel as it was.
|
||||
#[test]
|
||||
fn learn_show_rejects_a_missing_or_unknown_tutorial() {
|
||||
|
||||
@@ -424,6 +424,10 @@ pub struct UiState {
|
||||
/// is on screen. Not saved.
|
||||
#[serde(skip)]
|
||||
pub learn_locate: Option<crate::panels::learn::locate::Locate>,
|
||||
/// The tutorials whose practice samples were opened and the tutorials finished, kept while the
|
||||
/// app runs.
|
||||
#[serde(skip)]
|
||||
pub learn_practice: crate::panels::learn::Practice,
|
||||
pub control_bar: bool,
|
||||
/// The Control bar is docked to the bottom of the window instead of the top
|
||||
/// (`window.control.dock`).
|
||||
@@ -1089,6 +1093,7 @@ impl Default for UiState {
|
||||
find_font_in: String::new(),
|
||||
learn: Default::default(),
|
||||
learn_locate: None,
|
||||
learn_practice: Default::default(),
|
||||
control_bar: false,
|
||||
control_bar_bottom: false,
|
||||
control_bar_floating: false,
|
||||
|
||||
+23
-2
@@ -2688,13 +2688,34 @@ toggles it (`show` omitted or null), and answers whether it shows; Window › Le
|
||||
The panel counts as hidden while the dock is hidden or a full screen mode hides the panels. Showing it shows a hidden
|
||||
dock; in a full screen mode it makes Learn the front panel, which shows when the mode ends, and answers false. Any
|
||||
other value of `show` is an error and changes nothing. `learn.list {}` → `{skills: [{id, title, tutorials: [{id, title, steps}]}]}`, in the order the Skills tab
|
||||
lists them, where `steps` is the number of steps. `learn.show {tutorial}` shows the panel on its Skills tab with that
|
||||
lists them, where `steps` is the number of steps. `learn.skills {}` shows the panel on its Skills tab with the list of
|
||||
skills, with no skill and no tutorial open, as the Quick skill guide card and Back to skills do, and answers what
|
||||
`learn.list` answers. `learn.show {tutorial}` shows the panel on its Skills tab with that
|
||||
tutorial's steps → `{skill, tutorial, title, steps: [text]}`. A missing, non-string or unknown `tutorial` is an error
|
||||
and changes nothing. Behavior reference:
|
||||
and changes nothing.
|
||||
|
||||
Under a tutorial's steps, View more opens the tutorial's practice sample, a small drawing of VectorCraft's own with the
|
||||
art its steps work on, as a new untitled document that becomes the active one; Save asks where to save it. Once the
|
||||
sample is open, Finish tutorial marks the tutorial finished. A finished tutorial shows Next Up with the title of the
|
||||
next tutorial in the same skill, which Next Up shows without opening its sample. A skill's last tutorial, finished,
|
||||
says so and shows Back to skills instead; Next Up never moves to another skill. Which tutorials are finished is kept
|
||||
while the app runs, and the skill rings and counts do not show it yet. These are VectorCraft's own rules; the help
|
||||
page also plays a short video, which VectorCraft does not have. `learn.openSample {tutorial}` runs View more →
|
||||
`{tutorial, index, title}` of the new document, and moves the Learn view to that tutorial; each call opens another
|
||||
copy and leaves the open documents as they are. `learn.finish {tutorial}` runs Finish tutorial →
|
||||
`{tutorial, finished: true, next}`, where `next` is the next tutorial's id in the skill, or null after the skill's
|
||||
last one; the tutorial's sample must have been opened, and finishing it again keeps one entry. `learn.next {tutorial}`
|
||||
runs Next Up on a finished tutorial and shows the panel on the next one → `{skill, tutorial, title}`; on a tutorial
|
||||
not finished, or a skill's last tutorial, it is an error. For all three, a missing, non-string or unknown `tutorial`
|
||||
is an error, and an error changes nothing. Behavior reference:
|
||||
[Quick skill guide](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/learn-skills-quick-skill-guide.html).
|
||||
|
||||
```json
|
||||
{"name":"invoke_menu","arguments":{"command":"learn.show","params":{"tutorial":"pen-paths"}}}
|
||||
{"name":"invoke_menu","arguments":{"command":"learn.openSample","params":{"tutorial":"pen-paths"}}}
|
||||
{"name":"invoke_menu","arguments":{"command":"learn.finish","params":{"tutorial":"pen-paths"}}}
|
||||
{"name":"invoke_menu","arguments":{"command":"learn.next","params":{"tutorial":"pen-paths"}}}
|
||||
{"name":"invoke_menu","arguments":{"command":"learn.skills","params":{}}}
|
||||
```
|
||||
|
||||
A step that names a tool shows the tool's name as a blue link. A click on it, or Enter or Space while it has the
|
||||
|
||||
@@ -873,6 +873,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
- Native macOS menu bar, vector tool cursors, a ⌘K command palette, middle-button panning with any tool. Native menus explicitly end tracking on command selection, outside window presses and Escape; menu updates wait until tracking ends, and a dismissing press never starts a canvas gesture (M14.8).
|
||||
- The Learn panel (Window › Learn, or Explore tutorials beside the search icon on the app bar) has a For you tab with a Get started section and a Quick skill guide card, and a Skills tab that lists VectorCraft's own skills with their tutorial counts and empty progress rings; a skill lists its tutorials, a tutorial shows its numbered steps, and Back goes up a level; `learn.list` and `learn.show {tutorial}` script it ([the help page](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/learn-skills-quick-skill-guide.html); `panels::learn::tests`).
|
||||
- A tool's name in a Learn tutorial step is a blue link that shows where the tool is without choosing it (`learn.locateTool {tool}`): its toolbar button is marked, a collapsed group's flyout opens with the tool's row marked, a torn-off strip comes to the front, and a tool no shown toolbar holds is marked in the All Tools drawer. The mark, its 3 seconds and the hidden-toolbar cases are VectorCraft's own rules ([the help page](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/learn-skills-quick-skill-guide.html); `panels::learn::tests`, the `a_learn_link_*` tests in `toolbar::tests`).
|
||||
- Under a tutorial's steps, View more opens the tutorial's own practice sample as a new untitled document, and once the tutorial is finished, Next Up moves to the next tutorial in the same skill, as the help page describes ([the help page](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/learn-skills-quick-skill-guide.html)). The samples are VectorCraft's own drawings. A Finish tutorial button that shows once the sample is open, the untitled copy, Back to skills in place of Next Up after a skill's last tutorial, and Next Up showing the next tutorial without opening its sample are VectorCraft's own rules. `learn.openSample`, `learn.finish`, `learn.next` and `learn.skills` (Back to skills) script them. Not done: the help page's short video, and progress kept between sessions and shown in the skill rings. Regression tests: `view_more_opens_the_tutorial_s_sample_as_a_new_untitled_document`, `finishing_a_tutorial_offers_next_up_in_its_skill`, `the_practice_controls_work_from_the_keyboard`, `the_last_tutorial_of_a_skill_offers_back_to_skills`, `learn_finish_and_learn_next_follow_the_practice`, `learn_skills_shows_the_list_of_skills`, `the_practice_commands_reject_a_missing_or_unknown_tutorial`, `every_tutorial_has_its_own_readable_sample` and `the_next_tutorial_is_the_next_one_in_the_same_skill` in `crates/ui-egui/src/panels/learn/tests.rs`.
|
||||
- The errors of the blend spine commands (Move Spine Point, Reshape Spine Segment, Add Spine Point and Delete Spine Point) show in the status bar in the interface language: each has a row in every catalog that translates all status and error messages and in Bulgarian (`blend_spine_param_errors_are_translated`).
|
||||
- The errors of the 3D effects (Extrude & Bevel, Revolve, Inflate, Rotate and their Classic forms) show in the status bar in the interface language: the art a 3D effect cannot build a solid from, invalid light, graphics and surface settings, and the notes for an object that draws no 3D faces or more wireframe edges than the limit. Each has a row in every catalog that translates all status and error messages, and the effect's name and the values in the message are translated or kept as written (`three_d_refusals_are_translated_in_every_complete_catalog`, `three_d_refusals_translate_with_their_values`).
|
||||
- The errors of the parameter checks that many commands share show in the status bar in the interface language: a value that is not a number where a number goes (`object.move` with `"dx": "60 mm"`, and `object.rotate`, `object.setBounds` and the other commands that call `check_numbers`), a missing number or index, `ids` that are not a list of object ids, an object id that is not a non-negative integer or names no object, and a date that is not Unix seconds. Each has a row in every catalog that translates all status and error messages, and in Bulgarian, Danish, Indonesian and Dutch (`shared_param_check_errors_are_translated`).
|
||||
|
||||
+1
-1
@@ -382,7 +382,7 @@ next unit in the order a right-click on a ruler lists them, from Feet back to Po
|
||||
|---|---|---|
|
||||
| Effect > Warp Options: style previews, orientation choices and percent controls | done | #1760; focused input, mixed-style and history checks passed; original captures reviewed. No geometry or optional Properties changes. |
|
||||
| Command search: Up and Down move through the results, Enter runs the highlighted one | done | #1798 (#1757) |
|
||||
| Learn panel: Window › Learn and Explore tutorials on the app bar open it; For you holds a Quick skill guide card, Skills lists VectorCraft's own skills with tutorial counts and empty progress rings, a tutorial shows its numbered steps, Back goes up a level; a tool's name in a step is a link that marks the tool where the toolbars show it (its button, its group's flyout, a torn-off strip, a toolbar made with New Toolbar or the All Tools drawer) without choosing it | done | [the help page](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/learn-skills-quick-skill-guide.html); `learn.list`, `learn.show`, `learn.locateTool`; the mark's look and 3-second lifetime are VectorCraft's own rules; `panels::learn::tests`, `toolbar::tests` |
|
||||
| Learn panel: Window › Learn and Explore tutorials on the app bar open it; For you holds a Quick skill guide card, Skills lists VectorCraft's own skills with tutorial counts and empty progress rings, a tutorial shows its numbered steps, Back goes up a level; a tool's name in a step is a link that marks the tool where the toolbars show it (its button, its group's flyout, a torn-off strip, a toolbar made with New Toolbar or the All Tools drawer) without choosing it; View more opens the tutorial's practice sample as a new untitled document, Finish tutorial then marks it finished, and Next Up shows the next tutorial in the same skill (no video) | done | [the help page](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/learn-skills-quick-skill-guide.html); `learn.list`, `learn.skills`, `learn.show`, `learn.locateTool`, `learn.openSample`, `learn.finish`, `learn.next`; the mark's look and 3-second lifetime are VectorCraft's own rules; `panels::learn::tests`, `toolbar::tests` |
|
||||
| Pathfinder panel: a group selected alone counts its nested shapes, so an expanded stroked object keeps the Shape Modes enabled | done | #1642 (#1635) |
|
||||
| Selected Grid Repeat shows row, column and corner handles and spacing sliders; bounding-box handles take priority where they meet | done | #2691, `tools/src/repeat_grid.rs`; [public behavior reference](https://helpx.adobe.com/illustrator/desktop/paint-and-fill/create-and-edit-patterns/set-grid-repeat-options.html); drags preview and commit `object.repeat.options` as one undo step, Escape cancels; Radial Repeat has no handles yet |
|
||||
| Grid Repeat Options (Object › Repeat › Options…) has a Preview checkbox: the repeat on the canvas follows the fields while the dialog is open, Cancel puts the original back without a history step, OK keeps the previewed values as one undo step | done | #2651; [public behavior reference](https://helpx.adobe.com/illustrator/desktop/paint-and-fill/create-and-edit-patterns/set-grid-repeat-options.html) lists the Preview option for the grid options only, so Radial and Mirror Repeat Options have none; `dialogs/command.rs` tests |
|
||||
|
||||
Reference in New Issue
Block a user