Misc · Export and import: export, field mapping and a CSV importer
misc::ExportDialog offers the owner's formats as cards and hands the chosen one over for the owner to write. misc::ImportDialog maps the file's columns to the app's fields, matched by name to start, over a preview, and asks for each required field's column. misc::CsvImporter reads CSV as RFC 4180, lets the first row name the columns or not, and maps it through the ImportDialog into records; text that does not read says why.
This commit is contained in:
1 parent
cbba4ddeec
commit
79fe78cefd
9 files changed
+1023
-4
No files matched your search
@@ -10,13 +10,14 @@ use crate::ui::{section, specimen, specimens};
|
||||
mod asking;
|
||||
mod codes;
|
||||
mod notices;
|
||||
mod transfer;
|
||||
mod web;
|
||||
|
||||
pub const PAGE: Page = Page {
|
||||
number: 42,
|
||||
slug: "misc",
|
||||
title: "Misc",
|
||||
summary: "Small tools that stand alone: clocks, a stopwatch, a calculator, a unit converter, a QR code scanner, a captcha, a poll, a survey, a quiz, flashcards, licenses, terms, a cookie banner and web views.",
|
||||
summary: "Small tools that stand alone: clocks, a stopwatch, a calculator, a unit converter, a QR code scanner, a captcha, a poll, a survey, a quiz, flashcards, licenses, terms, a cookie banner, data out and in, and web views.",
|
||||
render,
|
||||
script: &[
|
||||
Step::DownAt("stopwatch-start", 30.0, 62.0),
|
||||
@@ -66,6 +67,14 @@ pub const PAGE: Page = Page {
|
||||
Step::UpAt("cookies", 230.0, 84.0),
|
||||
Step::HoverAt("cookies", 20.0, 20.0),
|
||||
Step::Shot("cookies-choosing"),
|
||||
Step::DownAt("open-export", 20.0, 16.0),
|
||||
Step::UpAt("open-export", 20.0, 16.0),
|
||||
Step::Shot("export"),
|
||||
Step::Key("escape"),
|
||||
Step::DownAt("open-csv", 20.0, 16.0),
|
||||
Step::UpAt("open-csv", 20.0, 16.0),
|
||||
Step::Shot("csv-import"),
|
||||
Step::Key("escape"),
|
||||
Step::DownAt("web", 240.0, 100.0),
|
||||
Step::UpAt("web", 240.0, 100.0),
|
||||
Step::DownAt("converter", 24.0, 60.0),
|
||||
@@ -89,6 +98,7 @@ fn render(window: &mut Window, cx: &mut App) -> AnyElement {
|
||||
.child(codes::render(window, cx))
|
||||
.child(asking::render(window, cx))
|
||||
.child(notices::render(window, cx))
|
||||
.child(transfer::render(window, cx))
|
||||
.child(web::render(cx))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
use ely_gpui_component::{
|
||||
buttons::Button,
|
||||
misc::{CsvImporter, ExportDialog, ExportFormat, ImportDialog, ImportField},
|
||||
theme::ActiveTheme,
|
||||
};
|
||||
use gpui::{App, IntoElement, ParentElement, SharedString, Styled, Window, div};
|
||||
|
||||
use crate::probe::probe;
|
||||
use crate::ui::section;
|
||||
|
||||
const PEOPLE: &str = "Name,Email,Team\nAda Lovelace,ada@example.com,Engines\n\"Lin, Wei\",lin@example.com,Maps\nGrace Hopper,grace@example.com,Compilers\n";
|
||||
|
||||
/// Which dialog is open.
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum Open {
|
||||
None,
|
||||
Export,
|
||||
Import,
|
||||
Csv,
|
||||
}
|
||||
|
||||
fn fields() -> [ImportField; 3] {
|
||||
[
|
||||
ImportField::new("name").required(),
|
||||
ImportField::new("email").required(),
|
||||
ImportField::new("phone"),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn render(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
let open = window.use_keyed_state("transfer-open", cx, |_, _| Open::None);
|
||||
let said = window.use_keyed_state("transfer-said", cx, |_, _| SharedString::default());
|
||||
let shut = |words: String,
|
||||
open: &gpui::Entity<Open>,
|
||||
said: &gpui::Entity<SharedString>,
|
||||
cx: &mut App| {
|
||||
open.update(cx, |open, cx| {
|
||||
*open = Open::None;
|
||||
cx.notify();
|
||||
});
|
||||
said.update(cx, |said, cx| {
|
||||
*said = words.into();
|
||||
cx.notify();
|
||||
});
|
||||
};
|
||||
let opener = |which: Open, label: &'static str, key: &'static str| {
|
||||
let open = open.clone();
|
||||
probe(
|
||||
key,
|
||||
div().child(Button::new(key, label).on_click(move |_, _, cx| {
|
||||
open.update(cx, |open, cx| {
|
||||
*open = which;
|
||||
cx.notify();
|
||||
})
|
||||
})),
|
||||
)
|
||||
};
|
||||
let (o, s) = (open.clone(), said.clone());
|
||||
let answer = move |words: String, cx: &mut App| shut(words, &o, &s, cx);
|
||||
let dialog = match *open.read(cx) {
|
||||
Open::None => None,
|
||||
Open::Export => {
|
||||
let (done, cancel) = (answer.clone(), answer.clone());
|
||||
Some(
|
||||
ExportDialog::new(
|
||||
"export",
|
||||
"Export the table",
|
||||
[
|
||||
ExportFormat::Pdf,
|
||||
ExportFormat::Png,
|
||||
ExportFormat::Csv,
|
||||
ExportFormat::Json,
|
||||
],
|
||||
move |format, _, cx| done(format!("Export as .{}", format.extension()), cx),
|
||||
move |_, cx| cancel("Export cancelled.".into(), cx),
|
||||
)
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
Open::Import => {
|
||||
let (done, cancel) = (answer.clone(), answer.clone());
|
||||
Some(
|
||||
ImportDialog::new(
|
||||
"import",
|
||||
"Import people",
|
||||
["Full name", "E-mail", "Team"],
|
||||
fields(),
|
||||
move |mapping, _, cx| done(format!("Columns by field: {mapping:?}"), cx),
|
||||
move |_, cx| cancel("Import cancelled.".into(), cx),
|
||||
)
|
||||
.preview([
|
||||
vec![
|
||||
"Ada Lovelace".into(),
|
||||
"ada@example.com".into(),
|
||||
"Engines".into(),
|
||||
],
|
||||
vec![
|
||||
"Grace Hopper".into(),
|
||||
"grace@example.com".into(),
|
||||
"Compilers".into(),
|
||||
],
|
||||
])
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
Open::Csv => {
|
||||
let (done, cancel) = (answer.clone(), answer.clone());
|
||||
Some(
|
||||
CsvImporter::new(
|
||||
"csv",
|
||||
"Import people.csv",
|
||||
PEOPLE,
|
||||
fields(),
|
||||
move |records, _, cx| done(format!("{} people imported.", records.len()), cx),
|
||||
move |_, cx| cancel("CSV import cancelled.".into(), cx),
|
||||
)
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
};
|
||||
div()
|
||||
.child(
|
||||
section(
|
||||
"ExportDialog / ImportDialog / CSVImporter",
|
||||
"Data out in a format the owner writes, and data in: columns mapped to the app's fields, matched by name to start, and a CSV file read into records through the same mapping.",
|
||||
cx,
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_wrap()
|
||||
.gap_2()
|
||||
.child(opener(Open::Export, "Export…", "open-export"))
|
||||
.child(opener(Open::Import, "Import…", "open-import"))
|
||||
.child(opener(Open::Csv, "Import people.csv…", "open-csv")),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_color(cx.theme().colors.fg_muted)
|
||||
.child(said.read(cx).clone()),
|
||||
),
|
||||
)
|
||||
.children(dialog)
|
||||
}
|
||||
Reference in new issue
Block a user