Skip to content

Commit

Permalink
Add support for importing CSV
Browse files Browse the repository at this point in the history
  • Loading branch information
david-swift committed Mar 30, 2024
1 parent afac0cd commit a49c0ff
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 37 deletions.
3 changes: 3 additions & 0 deletions Sources/Model/FlashcardsApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ enum FlashcardsApp: String, CaseIterable, Identifiable {

case quizlet
case anki
case csv

var id: String { rawValue }

Expand All @@ -18,6 +19,8 @@ enum FlashcardsApp: String, CaseIterable, Identifiable {
Loc.quizlet
case .anki:
Loc.anki
case .csv:
Loc.csv
}
}

Expand Down
12 changes: 12 additions & 0 deletions Sources/Model/Localized.yml
Original file line number Diff line number Diff line change
Expand Up @@ -733,3 +733,15 @@ searchFlashcards:
en: Search Flashcards
de: Karteikarten suchen
it: Cerca Flashcard

csv:
en: CSV
de: CSV

csvDescription:
en: Paste the text into the text field in the next view. Flashcards are separated by newlines, the front and back by commas.
de: Füge den Text in das Textfeld der nächsten Ansicht ein. Karteikarten werden mit Zeilenumbrüchen, die Vorder- und Rückseite mit Kommas getrennt.

importCSV:
en: Import CSV
de: CSV importieren
2 changes: 1 addition & 1 deletion Sources/View/EditView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ struct EditView: View {
} secondary: {
importFlashcards = true
}
.dialog(visible: $importFlashcards, width: 400, height: 500) {
.dialog(visible: $importFlashcards, width: 400, height: 450) {
ImportView(set: $set) { importFlashcards = false }
}
}
Expand Down
96 changes: 60 additions & 36 deletions Sources/View/ImportView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import RegexBuilder

struct ImportView: View {

// swiftlint:disable large_tuple
typealias FlashcardsRegex = Regex<(Substring, Substring, Substring)>
// swiftlint:enable large_tuple

@Binding var set: FlashcardsSet
@State private var text = ""
@State private var switchSides = false
Expand All @@ -28,10 +32,10 @@ struct ImportView: View {
.topToolbar {
toolbar(destination: destination)
}
case .paste:
case let .paste(app):
VStack {
entry
preview
preview(app: app)
}
.topToolbar {
toolbar(destination: destination)
Expand Down Expand Up @@ -75,37 +79,6 @@ struct ImportView: View {
.padding(20)
}

var preview: View {
ScrollView {
CarouselView(set: .constant(previewSet))
}
.vexpand()
}

var previewSet: FlashcardsSet {
var previewSet = set
previewSet.flashcards = []
let pattern = Regex {
Capture {
ZeroOrMore(.anyNonNewline)
}
"\t"
Capture {
ZeroOrMore(.anyNonNewline)
}
}
for (index, match) in text.matches(of: pattern).enumerated() {
let (front, back): (Substring, Substring)
if switchSides {
(_, back, front) = match.output
} else {
(_, front, back) = match.output
}
previewSet.flashcards.append(.init(id: "\(index)", front: .init(front), back: .init(back)))
}
return previewSet
}

var quizletTutorial: View {
StatusPage(
Loc.exportQuizletSet,
Expand All @@ -125,21 +98,72 @@ struct ImportView: View {
}
}

@ViewBuilder var ankiTutorial: View {
var ankiTutorial: View {
StatusPage(
Loc.exportAnkiDeck,
icon: .custom(name: "io.github.david_swift.Flashcards.set-symbolic"),
description: Loc.ankiDescription
)
}

var csvTutorial: View {
StatusPage(
Loc.importCSV,
icon: .default(icon: .emblemDocuments),
description: Loc.csvDescription
)
}

func preview(app: FlashcardsApp) -> View {
ScrollView {
CarouselView(set: .constant(previewSet(app: app)))
}
.vexpand()
}

func previewSet(app: FlashcardsApp) -> FlashcardsSet {
var previewSet = set
previewSet.flashcards = []
let pattern: FlashcardsRegex
switch app {
case .csv:
pattern = newlineRegex(separator: ",")
default:
pattern = newlineRegex(separator: "\t")
}
for (index, match) in text.matches(of: pattern).enumerated() {
let (front, back): (Substring, Substring)
if switchSides {
(_, back, front) = match.output
} else {
(_, front, back) = match.output
}
previewSet.flashcards.append(.init(id: "\(index)", front: .init(front), back: .init(back)))
}
return previewSet
}

func newlineRegex(separator: String) -> FlashcardsRegex {
.init {
Capture {
ZeroOrMore(.anyNonNewline)
}
separator
Capture {
ZeroOrMore(.anyNonNewline)
}
}
}

@ViewBuilder
func tutorial(app: FlashcardsApp) -> View {
switch app {
case .quizlet:
quizletTutorial
case .anki:
ankiTutorial
case .csv:
csvTutorial
}
}

Expand All @@ -160,8 +184,8 @@ struct ImportView: View {
}
}()
Button(label) {
if case .paste = destination {
set.flashcards += previewSet.flashcards
if case let .paste(app) = destination {
set.flashcards += previewSet(app: app).flashcards
close()
} else {
if case let .tutorial(app) = destination {
Expand Down

0 comments on commit a49c0ff

Please sign in to comment.