max / audiofiles
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
- Claude-Session
- https://claude.ai/code/session_01MptwXZ8k65v19rFmdGAyki
1 file changed,
+189 insertions,
-129 deletions
| @@ -153,15 +153,16 @@ | |||
| 153 | 153 | //! decide whether there is anything to confirm, and that is the app deciding | |
| 154 | 154 | //! when to ask rather than a fact about the question. | |
| 155 | 155 | ||
| 156 | - | use quasi_router::layout::{Family, FieldKind, Readiness, Tone}; | |
| 156 | + | use quasi_declare::declare; | |
| 157 | + | use quasi_router::layout::{Family, Tone}; | |
| 157 | 158 | use quasi_router::{ | |
| 158 | - | Accepted, Act, Action, Choice, Consult, Field, Figure, Locating, Meter, Node, Outcome, Prose, | |
| 159 | - | RegionKind, Request, Response, RouteError, Router, Row, Screen, Slot, Sought, | |
| 159 | + | Accepted, Act, Action, Choice, Consult, Figure, Locating, Node, Outcome, RegionKind, Request, | |
| 160 | + | Response, RouteError, Router, Screen, Slot, Sought, | |
| 160 | 161 | }; | |
| 161 | 162 | ||
| 162 | 163 | use super::{ | |
| 163 | - | Decision, Failure, FolderTags, Halted, Measure, Measures, Order, Panels, Reviewed, Stage, | |
| 164 | - | Strategy, Sweep, VaultChoice, | |
| 164 | + | Decision, Failure, FolderTags, Halted, Measure, Order, Panels, Reviewed, Stage, Strategy, | |
| 165 | + | Sweep, VaultChoice, | |
| 165 | 166 | }; | |
| 166 | 167 | ||
| 167 | 168 | /// The region the preflight answers into. | |
| @@ -270,7 +271,7 @@ | |||
| 270 | 271 | .post("/import/review/all/reject", reject_all) | |
| 271 | 272 | .post("/import/review/apply", apply_suggestions) | |
| 272 | 273 | .post("/import/review/discard", discard_suggestions) | |
| 273 | - | .post("/import/review/{at}/read", read) | |
| 274 | + | .post("/import/review/{at}/read", open_sample) | |
| 274 | 275 | .post("/import/review/{at}/judge", judge); | |
| 275 | 276 | ||
| 276 | 277 | let router = router | |
| @@ -293,39 +294,13 @@ | |||
| 293 | 294 | .waiting() | |
| 294 | 295 | .ok_or_else(|| RouteError::not_found("no import is waiting"))?; | |
| 295 | 296 | ||
| 296 | - | let body = Slot::new(BODY, RegionKind::Pane) | |
| 297 | - | .with(Node::page("Import folder")) | |
| 298 | - | .with(Node::text(format!( | |
| 299 | - | "About to import {} audio file{} (~{}) from {}", | |
| 300 | - | waiting.files, | |
| 301 | - | if waiting.files == 1 { "" } else { "s" }, | |
| 302 | - | waiting.size, | |
| 303 | - | waiting.source, | |
| 304 | - | ))) | |
| 305 | - | // The reassurance the shipped modal draws as a small muted line. It is a | |
| 306 | - | // fact about the whole operation rather than about either answer, so it | |
| 307 | - | // is prose and not a field's hint. | |
| 308 | - | .with(Node::text( | |
| 309 | - | "Files stay where they are: audiofiles only indexes them.", | |
| 310 | - | )) | |
| 311 | - | .with(Node::Form { | |
| 312 | - | // The checkbox the shipped modal keeps on `BrowserState`. See the | |
| 313 | - | // module header: here it is submitted with the answer it qualifies. | |
| 314 | - | fields: vec![Field::new( | |
| 315 | - | FieldKind::Checkbox, | |
| 316 | - | AGAIN, | |
| 317 | - | "Don't ask again for folders this size", | |
| 318 | - | )], | |
| 319 | - | submit: "Import".to_owned(), | |
| 320 | - | action: Action::post("/import/preflight"), | |
| 321 | - | }) | |
| 322 | - | .with(Node::Act( | |
| 323 | - | Act::new("Cancel", Action::post("/import/preflight/cancel")).key("esc"), | |
| 324 | - | )); | |
| 325 | - | ||
| 326 | - | Ok(Response::from(Outcome::Over( | |
| 327 | - | Screen::sidebar_content("Import folder").with(body), | |
| 328 | - | ))) | |
| 297 | + | Ok(Response::from(Outcome::Over(preflight(&format!( | |
| 298 | + | "About to import {} audio file{} (~{}) from {}", | |
| 299 | + | waiting.files, | |
| 300 | + | plural(waiting.files), | |
| 301 | + | waiting.size, | |
| 302 | + | waiting.source, | |
| 303 | + | ))))) | |
| 329 | 304 | } | |
| 330 | 305 | ||
| 331 | 306 | /// `POST /import/preflight` | |
| @@ -350,7 +325,7 @@ | |||
| 350 | 325 | ||
| 351 | 326 | /// `GET /import` | |
| 352 | 327 | fn index(state: &Panels<'_>, _request: Request) -> Result<Response, RouteError> { | |
| 353 | - | Ok(flow(state).into()) | |
| 328 | + | Ok(showing(state)) | |
| 354 | 329 | } | |
| 355 | 330 | ||
| 356 | 331 | /// `GET /import/open` | |
| @@ -545,7 +520,7 @@ | |||
| 545 | 520 | return Err(RouteError::not_found("no such import strategy")); | |
| 546 | 521 | } | |
| 547 | 522 | state.importing.decide(decision, value); | |
| 548 | - | Ok(flow(state).into()) | |
| 523 | + | Ok(showing(state)) | |
| 549 | 524 | } | |
| 550 | 525 | ||
| 551 | 526 | /// `POST /import/start` | |
| @@ -569,25 +544,25 @@ | |||
| 569 | 544 | return Err(RouteError::not_found(reason)); | |
| 570 | 545 | } | |
| 571 | 546 | state.importing.begin(); | |
| 572 | - | Ok(flow(state).into()) | |
| 547 | + | Ok(showing(state)) | |
| 573 | 548 | } | |
| 574 | 549 | ||
| 575 | 550 | /// `POST /import/stop` | |
| 576 | 551 | fn stop(state: &Panels<'_>, _request: Request) -> Result<Response, RouteError> { | |
| 577 | 552 | state.importing.stop(); | |
| 578 | - | Ok(flow(state).into()) | |
| 553 | + | Ok(showing(state)) | |
| 579 | 554 | } | |
| 580 | 555 | ||
| 581 | 556 | /// `POST /import/retry` | |
| 582 | 557 | fn retry(state: &Panels<'_>, _request: Request) -> Result<Response, RouteError> { | |
| 583 | 558 | state.importing.retry(); | |
| 584 | - | Ok(flow(state).into()) | |
| 559 | + | Ok(showing(state)) | |
| 585 | 560 | } | |
| 586 | 561 | ||
| 587 | 562 | /// `POST /import/dismiss` | |
| 588 | 563 | fn dismiss(state: &Panels<'_>, _request: Request) -> Result<Response, RouteError> { | |
| 589 | 564 | state.importing.dismiss(); | |
| 590 | - | Ok(flow(state).into()) | |
| 565 | + | Ok(showing(state)) | |
| 591 | 566 | } | |
| 592 | 567 | ||
| 593 | 568 | /// `POST /import/folders/{at}/tags` | |
| @@ -595,7 +570,7 @@ | |||
| 595 | 570 | let at = folder_at(state, &request)?; | |
| 596 | 571 | let typed = request.payload.get(TAGS).unwrap_or_default(); | |
| 597 | 572 | state.importing.tag_folder(at, typed); | |
| 598 | - | Ok(flow(state).into()) | |
| 573 | + | Ok(showing(state)) | |
| 599 | 574 | } | |
| 600 | 575 | ||
| 601 | 576 | /// `POST /import/folders/all` | |
| @@ -612,7 +587,7 @@ | |||
| 612 | 587 | return Err(RouteError::not_found("type at least one tag to apply")); | |
| 613 | 588 | } | |
| 614 | 589 | state.importing.tag_every_folder(typed); | |
| 615 | - | Ok(flow(state).into()) | |
| 590 | + | Ok(showing(state)) | |
| 616 | 591 | } | |
| 617 | 592 | ||
| 618 | 593 | /// `POST /import/folders/apply` | |
| @@ -628,13 +603,13 @@ | |||
| 628 | 603 | return Err(RouteError::not_found("nothing has been typed to apply")); | |
| 629 | 604 | } | |
| 630 | 605 | state.importing.apply_folder_tags(); | |
| 631 | - | Ok(flow(state).into()) | |
| 606 | + | Ok(showing(state)) | |
| 632 | 607 | } | |
| 633 | 608 | ||
| 634 | 609 | /// `POST /import/folders/skip` | |
| 635 | 610 | fn skip_folder_tags(state: &Panels<'_>, _request: Request) -> Result<Response, RouteError> { | |
| 636 | 611 | state.importing.skip_folder_tags(); | |
| 637 | - | Ok(flow(state).into()) | |
| 612 | + | Ok(showing(state)) | |
| 638 | 613 | } | |
| 639 | 614 | ||
| 640 | 615 | /// `POST /import/measure/{measure}` | |
| @@ -647,13 +622,13 @@ | |||
| 647 | 622 | // would then have to agree with three renderers about. | |
| 648 | 623 | let wanted = !request.payload.get(name).unwrap_or_default().is_empty(); | |
| 649 | 624 | state.importing.measure(measure, wanted); | |
| 650 | - | Ok(flow(state).into()) | |
| 625 | + | Ok(showing(state)) | |
| 651 | 626 | } | |
| 652 | 627 | ||
| 653 | 628 | /// `POST /import/analyse` | |
| 654 | 629 | fn analyse(state: &Panels<'_>, _request: Request) -> Result<Response, RouteError> { | |
| 655 | 630 | state.importing.analyse(); | |
| 656 | - | Ok(flow(state).into()) | |
| 631 | + | Ok(showing(state)) | |
| 657 | 632 | } | |
| 658 | 633 | ||
| 659 | 634 | /// `POST /import/analyse/back` | |
| @@ -671,25 +646,25 @@ | |||
| 671 | 646 | )); | |
| 672 | 647 | } | |
| 673 | 648 | state.importing.back_to_tagging(); | |
| 674 | - | Ok(flow(state).into()) | |
| 649 | + | Ok(showing(state)) | |
| 675 | 650 | } | |
| 676 | 651 | ||
| 677 | 652 | /// `POST /import/analyse/skip` | |
| 678 | 653 | fn skip_analysis(state: &Panels<'_>, _request: Request) -> Result<Response, RouteError> { | |
| 679 | 654 | state.importing.skip_analysis(); | |
| 680 | - | Ok(flow(state).into()) | |
| 655 | + | Ok(showing(state)) | |
| 681 | 656 | } | |
| 682 | 657 | ||
| 683 | 658 | /// `POST /import/analyse/stop` | |
| 684 | 659 | fn stop_analysis(state: &Panels<'_>, _request: Request) -> Result<Response, RouteError> { | |
| 685 | 660 | state.importing.stop_analysis(); | |
| 686 | - | Ok(flow(state).into()) | |
| 661 | + | Ok(showing(state)) | |
| 687 | 662 | } | |
| 688 | 663 | ||
| 689 | 664 | /// `POST /import/analyse/retry` | |
| 690 | 665 | fn retry_analysis(state: &Panels<'_>, _request: Request) -> Result<Response, RouteError> { | |
| 691 | 666 | state.importing.retry_analysis(); | |
| 692 | - | Ok(flow(state).into()) | |
| 667 | + | Ok(showing(state)) | |
| 693 | 668 | } | |
| 694 | 669 | ||
| 695 | 670 | /// `POST /import/review/order` | |
| @@ -701,14 +676,14 @@ | |||
| 701 | 676 | .unwrap_or_default(); | |
| 702 | 677 | let order = Order::from_key(chosen).ok_or_else(|| RouteError::not_found("no such order"))?; | |
| 703 | 678 | state.importing.order(order); | |
| 704 | - | Ok(flow(state).into()) | |
| 679 | + | Ok(showing(state)) | |
| 705 | 680 | } | |
| 706 | 681 | ||
| 707 | 682 | /// `POST /import/review/{at}/read` | |
| 708 | - | fn read(state: &Panels<'_>, request: Request) -> Result<Response, RouteError> { | |
| 683 | + | fn open_sample(state: &Panels<'_>, request: Request) -> Result<Response, RouteError> { | |
| 709 | 684 | let at = reviewed_at(state, &request)?; | |
| 710 | 685 | state.importing.read(at); | |
| 711 | - | Ok(flow(state).into()) | |
| 686 | + | Ok(showing(state)) | |
| 712 | 687 | } | |
| 713 | 688 | ||
| 714 | 689 | /// `POST /import/review/{at}/judge` | |
| @@ -736,19 +711,19 @@ | |||
| 736 | 711 | .and_then(|item| item.suggestions.iter().find(|held| held.tag == tag)) | |
| 737 | 712 | .ok_or_else(|| RouteError::not_found("no such suggestion"))?; | |
| 738 | 713 | state.importing.judge(at, tag, !held.accepted); | |
| 739 | - | Ok(flow(state).into()) | |
| 714 | + | Ok(showing(state)) | |
| 740 | 715 | } | |
| 741 | 716 | ||
| 742 | 717 | /// `POST /import/review/all/accept` | |
| 743 | 718 | fn accept_all(state: &Panels<'_>, _request: Request) -> Result<Response, RouteError> { | |
| 744 | 719 | state.importing.judge_all(true); | |
| 745 | - | Ok(flow(state).into()) | |
| 720 | + | Ok(showing(state)) | |
| 746 | 721 | } | |
| 747 | 722 | ||
| 748 | 723 | /// `POST /import/review/all/reject` | |
| 749 | 724 | fn reject_all(state: &Panels<'_>, _request: Request) -> Result<Response, RouteError> { | |
| 750 | 725 | state.importing.judge_all(false); | |
| 751 | - | Ok(flow(state).into()) | |
| 726 | + | Ok(showing(state)) | |
| 752 | 727 | } | |
| 753 | 728 | ||
| 754 | 729 | /// `POST /import/review/apply` | |
| @@ -764,25 +739,25 @@ | |||
| 764 | 739 | return Err(RouteError::not_found("no suggestions have been accepted")); | |
| 765 | 740 | } | |
| 766 | 741 | state.importing.apply_suggestions(); | |
| 767 | - | Ok(flow(state).into()) | |
| 742 | + | Ok(showing(state)) | |
| 768 | 743 | } | |
| 769 | 744 | ||
| 770 | 745 | /// `POST /import/review/discard` | |
| 771 | 746 | fn discard_suggestions(state: &Panels<'_>, _request: Request) -> Result<Response, RouteError> { | |
| 772 | 747 | state.importing.discard_suggestions(); | |
| 773 | - | Ok(flow(state).into()) | |
| 748 | + | Ok(showing(state)) | |
| 774 | 749 | } | |
| 775 | 750 | ||
| 776 | 751 | /// `POST /import/summary/keep` | |
| 777 | 752 | fn keep_failed(state: &Panels<'_>, _request: Request) -> Result<Response, RouteError> { | |
| 778 | 753 | state.importing.keep_failed(); | |
| 779 | - | Ok(flow(state).into()) | |
| 754 | + | Ok(showing(state)) | |
| 780 | 755 | } | |
| 781 | 756 | ||
| 782 | 757 | /// `POST /import/summary/purge` | |
| 783 | 758 | fn purge_all(state: &Panels<'_>, _request: Request) -> Result<Response, RouteError> { | |
| 784 | 759 | state.importing.purge_failed(None); | |
| 785 | - | Ok(flow(state).into()) | |
| 760 | + | Ok(showing(state)) | |
| 786 | 761 | } | |
| 787 | 762 | ||
| 788 | 763 | /// `POST /import/summary/{at}/purge` | |
| @@ -799,7 +774,7 @@ | |||
| 799 | 774 | return Err(RouteError::not_found("no such file")); | |
| 800 | 775 | } | |
| 801 | 776 | state.importing.purge_failed(Some(at)); | |
| 802 | - | Ok(flow(state).into()) | |
| 777 | + | Ok(showing(state)) | |
| 803 | 778 | } | |
| 804 | 779 | ||
| 805 | 780 | /// `GET /cleanup` | |
| @@ -812,171 +787,517 @@ | |||
| 812 | 787 | .importing | |
| 813 | 788 | .sweeping() | |
| 814 | 789 | .ok_or_else(|| RouteError::not_found("nothing is being cleaned up"))?; | |
| 815 | - | Ok(Screen::sidebar_content("Cleaning up") | |
| 816 | - | .with(sweep_body(&sweep)) | |
| 817 | - | .into()) | |
| 790 | + | Ok(cleanup(&cleaning(Some(&sweep))).into()) | |
| 818 | 791 | } | |
| 819 | 792 | ||
| 820 | 793 | /// `POST /cleanup/stop` | |
| 821 | 794 | fn stop_sweep(state: &Panels<'_>, _request: Request) -> Result<Response, RouteError> { | |
| 822 | 795 | state.importing.stop_sweep(); | |
| 823 | - | Ok(Screen::sidebar_content("Cleaning up") | |
| 824 | - | .with(match state.importing.sweeping() { | |
| 825 | - | Some(sweep) => sweep_body(&sweep), | |
| 826 | - | None => { | |
| 827 | - | Slot::new(SWEEP, RegionKind::Pane).with(Node::empty("Nothing is being cleaned up.")) | |
| 828 | - | } | |
| 829 | - | }) | |
| 830 | - | .into()) | |
| 796 | + | Ok(cleanup(&cleaning(state.importing.sweeping().as_ref())).into()) | |
| 831 | 797 | } | |
| 832 | 798 | ||
| 833 | 799 | // --- the screens --- | |
| 834 | 800 | ||
| 835 | - | /// Whichever of the nine screens the flow is on. | |
| 836 | - | fn flow(state: &Panels<'_>) -> Screen { | |
| 837 | - | let body = match state.importing.stage() { | |
| 838 | - | Stage::Idle => idle(), | |
| 839 | - | Stage::Configuring { | |
| 840 | - | source, | |
| 841 | - | files, | |
| 842 | - | strategy, | |
| 843 | - | vault_name, | |
| 844 | - | vaults, | |
| 845 | - | merging_into, | |
| 846 | - | } => configuring(&source, files, strategy, &vault_name, &vaults, merging_into), | |
| 847 | - | Stage::Scanning { found, size } => scanning(found, size.as_deref()), | |
| 848 | - | Stage::Copying { | |
| 849 | - | done, | |
| 850 | - | total, | |
| 851 | - | current, | |
| 852 | - | size, | |
| 853 | - | in_place, | |
| 854 | - | failures, | |
| 855 | - | } => copying(done, total, ¤t, size.as_deref(), in_place, &failures), | |
| 856 | - | Stage::Tagging { folders } => tagging(&folders), | |
| 857 | - | Stage::Choosing { | |
| 858 | - | samples, | |
| 859 | - | measures, | |
| 860 | - | resumable, | |
| 861 | - | } => choosing(samples, measures, resumable), | |
| 862 | - | Stage::Analysing { | |
| 863 | - | done, | |
| 864 | - | total, | |
| 865 | - | current, | |
| 866 | - | failures, | |
| 867 | - | } => analysing(done, total, ¤t, &failures), | |
| 868 | - | Stage::Reviewing { items, at, order } => reviewing(&items, at, order), | |
| 869 | - | Stage::Summary { | |
| 870 | - | rejected, | |
| 871 | - | unanalysed, | |
| 872 | - | } => summary(&rejected, &unanalysed), | |
| 873 | - | Stage::Stopped { what, done, total } => stopped(what, done, total), | |
| 874 | - | }; | |
| 875 | - | Screen::sidebar_content("Import").with(body) | |
| 801 | + | /// The flow, read and then described. | |
| 802 | + | fn showing(state: &Panels<'_>) -> Response { | |
| 803 | + | Response::from(flow(&read(state))) | |
| 876 | 804 | } | |
| 877 | 805 | ||
| 878 | - | /// Nothing is being imported. | |
| 806 | + | /// Whichever of the nine screens the flow is on. | |
| 879 | 807 | /// | |
| 880 | - | /// A way in rather than a bare sentence, which is where this screen differs from | |
| 881 | - | /// [`export`](super::export)'s idle: an export is entered by selecting samples in | |
| 882 | - | /// a different screen and there is nothing honest to offer here, while an import | |
| 883 | - | /// is entered by choosing a folder and that is a control. | |
| 884 | - | fn idle() -> Slot { | |
| 885 | - | Slot::new(FLOW, RegionKind::Pane).with( | |
| 886 | - | Node::empty("Nothing is being imported.") | |
| 887 | - | .offering(Act::new("Import...", Action::get("/import/open"))), | |
| 888 | - | ) | |
| 808 | + | /// Nine members rather than nine returns, which is [`export`](super::export)'s | |
| 809 | + | /// shape: the region is one region, the flow is at exactly one stage, and the | |
| 810 | + | /// description says which by looping over the member that is there. | |
| 811 | + | struct Flow { | |
| 812 | + | /// Where the flow is, as prose. | |
| 813 | + | /// | |
| 814 | + | /// The degraded stage rail, and the reason it sits here rather than on each | |
| 815 | + | /// stage: which of four steps is up is a fact about the flow, and seven of | |
| 816 | + | /// the nine screens say it first. See the module header, | |
| 817 | + | /// `quasi:vocabulary:unchosen-stage`. | |
| 818 | + | rail: Option<String>, | |
| 819 | + | /// Whether nothing is being imported. | |
| 820 | + | idle: bool, | |
| 821 | + | configuring: Option<Configuring>, | |
| 822 | + | scanning: Option<Scanning>, | |
| 823 | + | copying: Option<Copying>, | |
| 824 | + | tagging: Option<Tagging>, | |
| 825 | + | choosing: Option<Choosing>, | |
| 826 | + | analysing: Option<Analysing>, | |
| 827 | + | reviewing: Option<Reviewing>, | |
| 828 | + | summary: Option<Summary>, | |
| 829 | + | stopped: Option<Stopped>, | |
| 889 | 830 | } | |
| 890 | 831 | ||
| 891 | 832 | /// Where the files are going, before any of them go. | |
| 892 | - | fn configuring( | |
| 833 | + | struct Configuring { | |
| 834 | + | /// The folder they are coming from. | |
| 835 | + | source: String, | |
| 836 | + | /// How many audio files the dry-run scan found in it. | |
| 837 | + | found: String, | |
| 838 | + | /// What counts as one, and what happens to the ones already held. | |
| 839 | + | supported: String, | |
| 840 | + | /// The three answers, the third carrying the reason it cannot be taken | |
| 841 | + | /// where it cannot. | |
| 842 | + | strategies: Vec<Choice>, | |
| 843 | + | /// Which of them is chosen. | |
| 844 | + | strategy: &'static str, | |
| 845 | + | /// Naming a new vault, while that is the answer. | |
| 846 | + | vault: Option<NewVault>, | |
| 847 | + | /// Picking a vault to merge into, while that is the answer and there are | |
| 848 | + | /// any. | |
| 849 | + | merge: Option<Merge>, | |
| 850 | + | /// Whether a merge was chosen with nothing to merge into. | |
| 851 | + | /// | |
| 852 | + | /// Reachable only by arriving here with the strategy already set, since the | |
| 853 | + | /// choice itself carries its own refusal. Said as a banner rather than as an | |
| 854 | + | /// error on a picker that has nothing to pick from. | |
| 855 | + | nowhere: bool, | |
| 856 | + | /// Whether the import may start. | |
| 857 | + | ready: bool, | |
| 858 | + | } | |
| 859 | + | ||
| 860 | + | /// A new vault, named. | |
| 861 | + | struct NewVault { | |
| 862 | + | /// What has been typed. | |
| 863 | + | typed: String, | |
| 864 | + | /// What is wrong with it, while something is. | |
| 865 | + | /// | |
| 866 | + | /// The state the shipped screen used to leave unexplained: Import disabled | |
| 867 | + | /// itself and said why only to a pointer that hovered it. | |
| 868 | + | problem: Option<&'static str>, | |
| 869 | + | } | |
| 870 | + | ||
| 871 | + | /// An existing vault, merged into. | |
| 872 | + | struct Merge { | |
| 873 | + | /// Every vault, by position. | |
| 874 | + | options: Vec<Choice>, | |
| 875 | + | /// Which is chosen. | |
| 876 | + | chosen: String, | |
| 877 | + | } | |
| 878 | + | ||
| 879 | + | /// Walking the folder, before there is a count. | |
| 880 | + | struct Scanning { | |
| 881 | + | /// How far the walk has got, however much of that is known. | |
| 882 | + | said: String, | |
| 883 | + | } | |
| 884 | + | ||
| 885 | + | /// Files being copied in. | |
| 886 | + | struct Copying { | |
| 887 | + | /// What the whole set weighs, and which of the two that means. | |
| 888 | + | weight: Option<Weight>, | |
| 889 | + | /// How many have landed, against how many there are. | |
| 890 | + | gauge: Gauge, | |
| 891 | + | /// The one being copied now, while one is named. | |
| 892 | + | current: Option<String>, | |
| 893 | + | /// What has gone wrong so far, while anything has. | |
| 894 | + | trouble: Option<Trouble>, | |
| 895 | + | } | |
| 896 | + | ||
| 897 | + | /// What the set weighs, and whether that is disk being spent. | |
| 898 | + | /// | |
| 899 | + | /// Which of the two this says is the whole point of the line: an import that | |
| 900 | + | /// references files where they sit costs no disk, and one that copies them costs | |
| 901 | + | /// this much. | |
| 902 | + | struct Weight { | |
| 903 | + | said: String, | |
| 904 | + | tone: Tone, | |
| 905 | + | } | |
| 906 | + | ||
| 907 | + | /// Naming what came in. | |
| 908 | + | struct Tagging { | |
| 909 | + | /// How many folders, and how many samples in them. | |
| 910 | + | said: String, | |
| 911 | + | /// Each folder, with whatever has been typed against it. | |
| 912 | + | folders: Vec<Folder>, | |
| 913 | + | /// Whether nothing has been typed anywhere, which deadens Apply. | |
| 914 | + | nothing_typed: bool, | |
| 915 | + | } | |
| 916 | + | ||
| 917 | + | /// One imported folder, and the tags typed against it. | |
| 918 | + | struct Folder { | |
| 919 | + | /// Its position, which is both its region's name and its address. | |
| 920 | + | at: usize, | |
| 921 | + | /// What it is called, and how much is in it. | |
| 922 | + | heading: String, | |
| 923 | + | /// What has been typed. | |
| 924 | + | typed: String, | |
| 925 | + | /// The tags that are not tags, while any were typed. | |
| 926 | + | invalid: Option<String>, | |
| 927 | + | } | |
| 928 | + | ||
| 929 | + | /// What the analysis will measure. | |
| 930 | + | struct Choosing { | |
| 931 | + | /// How many samples it would run over. | |
| 932 | + | said: String, | |
| 933 | + | /// Every measure, ticked or not. | |
| 934 | + | measures: Vec<Ticked>, | |
| 935 | + | /// Whether the tagging step can be returned to. | |
| 936 | + | resumable: bool, | |
| 937 | + | } | |
| 938 | + | ||
| 939 | + | /// One measure, and whether it is on. | |
| 940 | + | struct Ticked { | |
| 941 | + | name: &'static str, | |
| 942 | + | label: &'static str, | |
| 943 | + | /// On or off, as the control carries it. | |
| 944 | + | value: &'static str, | |
| 945 | + | } | |
| 946 | + | ||
| 947 | + | /// Samples being analysed. | |
| 948 | + | struct Analysing { | |
| 949 | + | /// How many are done, against how many there are. | |
| 950 | + | gauge: Gauge, | |
| 951 | + | /// The one being analysed now, while one is named. | |
| 952 | + | current: Option<String>, |
Lines truncated