can request separate books
							parent
							
								
									9eb488a87a
								
							
						
					
					
						commit
						32696d7b58
					
				|  | @ -5,6 +5,8 @@ | |||
| //  Created by Saint on 5/20/24. | ||||
| // | ||||
| 
 | ||||
| import GRDB | ||||
| import GRDBQuery | ||||
| import Foundation | ||||
| import SwiftUI | ||||
| import WrappingHStack | ||||
|  | @ -14,8 +16,11 @@ struct SegRow: View { | |||
|     var ribbonId: Int64 | ||||
|     @State var highlights = Set<Int>() | ||||
| 
 | ||||
|     let intraWordSpacing = CGFloat(1.6) | ||||
|     var body: some View { | ||||
|         var segSplit = seg.body.components(separatedBy: ";;") | ||||
|         Print("got here meow") | ||||
|         Print(segSplit) | ||||
|         let decoder = JSONDecoder() | ||||
|         var retView = WrappingHStack(alignment: .leading, horizontalSpacing: 0) { | ||||
|             ForEach(0 ..< segSplit.count, id: \.self) { segIndex in | ||||
|  | @ -53,7 +58,8 @@ struct SegRow: View { | |||
|                         Text(arrayOfText[index]) | ||||
|                             .foregroundColor(Color(UIColor(red: 0.76, green: 0.76, blue: 0.76, alpha: 1.00))) | ||||
|                             .font(Font.custom("AveriaSerifLibre-Regular", size: fontSize)) | ||||
|                             .padding(.horizontal, 1.5) // intra word spacing | ||||
| 
 | ||||
|                             .padding(.horizontal, intraWordSpacing) // intra word spacing | ||||
|                             .if(self.highlights.contains(verse.verse)) { $0.background(Color(hex: highlightColor)) } | ||||
|                             .foregroundColor(Color.white) | ||||
|                             .onTapGesture { | ||||
|  | @ -79,7 +85,6 @@ struct SegRow: View { | |||
| struct Pane: View { | ||||
|     @ObservedObject var paneConnector: PaneConnector | ||||
| 
 | ||||
|     @State var segs: [SegDenorm] | ||||
|     @State var selectedRibbon: [Ribbon] | ||||
| 
 | ||||
|     @State var width: CGFloat | ||||
|  | @ -89,6 +94,8 @@ struct Pane: View { | |||
| 
 | ||||
|     @State var refresh: Bool = false | ||||
| 
 | ||||
|     @Query(SegDenormRequest(book: "bible.mark")) private var segs: [SegDenorm] | ||||
| 
 | ||||
|     @Environment(\.appDatabase) private var appDatabase | ||||
| 
 | ||||
|     // var handleVisibilityChanged: (String, VisibilityChange,  VisibilityTracker<String>) -> Void | ||||
|  | @ -125,6 +132,9 @@ struct Pane: View { | |||
| 
 | ||||
|                     Task { | ||||
|                         DispatchQueue.main.async { | ||||
|                             if paneConnector.visibilityTracker == nil { | ||||
|                                 return | ||||
|                             } | ||||
|                             let gTracker = paneConnector.visibilityTracker! | ||||
| 
 | ||||
|                             Print("scroll Id target: \(paneConnector.scrollId)") | ||||
|  |  | |||
|  | @ -11,9 +11,9 @@ struct SegDenormRequest: Queryable { | |||
|     var book: String | ||||
| 
 | ||||
|     // MARK: - Queryable Implementation | ||||
|      | ||||
| 
 | ||||
|     static var defaultValue: [SegDenorm] { [] } | ||||
|      | ||||
| 
 | ||||
|     func publisher(in appDatabase: AppDatabase) -> AnyPublisher<[SegDenorm], Error> { | ||||
|         ValueObservation | ||||
|             .tracking(fetchValue(_:)) | ||||
|  | @ -22,17 +22,39 @@ struct SegDenormRequest: Queryable { | |||
|                 scheduling: .immediate) | ||||
|             .eraseToAnyPublisher() | ||||
|     } | ||||
|      | ||||
|     func fetchValue(_ db: Database) throws -> [SegDenorm] { | ||||
|         print("WOOOOOOF") | ||||
|         var sql = "select seg_id as id, seg.book as book, group_concat(line.body, ';;') as body from seg join line on seg.line_id = line.rowid WHERE seg.book = 'bible.john' group by seg.seg_id" | ||||
| 
 | ||||
|         do { | ||||
|             var ret =  try SegDenorm.fetchAll(db, sql: sql) // [Player] | ||||
|              | ||||
|         // print("SEGS DENORM") | ||||
|         // print(ret) | ||||
|     func fetchValue(_ db: Database) throws -> [SegDenorm] { | ||||
|         print("segs denorm fetching for \(book)") | ||||
|         print(book) | ||||
|         var sql = """ | ||||
|         select seg_id as id, seg.book as book, group_concat(line.body, ';;') as body from \ | ||||
|         (select * from seg  where seg.book = '\(book)') as seg \ | ||||
|         join (select * from line  where line.book = '\(book)') as line \ | ||||
|         on seg.line_id = line.line_id  group by seg.seg_id | ||||
|         """ | ||||
| 
 | ||||
|         do | ||||
|         { | ||||
|             var ret =  try SegDenorm.fetchAll(db, sql: sql) | ||||
| 
 | ||||
|         print("SEGS DENORM") | ||||
|         print(ret[0]) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|         // var sql2 = """ | ||||
|         // select count(1) from seg where seg.book = '\(book)' | ||||
|         // """ | ||||
| 
 | ||||
|         //     var ret2 =  try SegDenorm.fetchAll(db, sql: sql2) | ||||
| 
 | ||||
|         // print("test sql result") | ||||
|         // print(ret2[0]) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|         return ret | ||||
| 
 | ||||
|     } catch let error { | ||||
|         print(error.localizedDescription) | ||||
|         print(error) | ||||
|  |  | |||
|  | @ -24,7 +24,7 @@ struct SelectedRibbonRequest: Queryable { | |||
|     /// The ordering used by the player request. | ||||
|     // var ordering: Ordering | ||||
|     static var defaultValue: [Ribbon] { [] } | ||||
|      | ||||
| 
 | ||||
|     func publisher(in appDatabase: AppDatabase) -> AnyPublisher<[Ribbon], Error> { | ||||
|         // Build the publisher from the general-purpose read-only access | ||||
|         // granted by `appDatabase.reader`. | ||||
|  | @ -39,7 +39,7 @@ struct SelectedRibbonRequest: Queryable { | |||
|                 scheduling: .immediate) | ||||
|             .eraseToAnyPublisher() | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     // This method is not required by Queryable, but it makes it easier | ||||
|     func fetchValue(_ db: Database) throws -> [Ribbon] { | ||||
| 
 | ||||
|  | @ -51,8 +51,6 @@ struct SelectedRibbonRequest: Queryable { | |||
|         // var ret3 =  try Ribbon.fetchAll(db, sql: "SELECT * FROM Ribbon") // [Player] | ||||
|         // print(ret3) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|         // print("FETCH JOIN RIBBON") | ||||
|         var ret =  try Ribbon.fetchAll(db, sql: "SELECT Ribbon.* FROM SelectedRibbon join Ribbon on SelectedRibbon.ribbonId = ribbon.rowId WHERE SelectedRibbon.rowId = 1") // [Player] | ||||
|         // print(ret) | ||||
|  |  | |||
|  | @ -11,7 +11,7 @@ struct AppDatabase { | |||
|         self.dbWriter = dbWriter | ||||
|         try migrator.migrate(dbWriter) | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     /// Provides access to the database. | ||||
|     /// | ||||
|     /// Application can use a `DatabasePool`, while SwiftUI previews and tests | ||||
|  | @ -19,19 +19,19 @@ struct AppDatabase { | |||
|     /// | ||||
|     /// See <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/databaseconnections> | ||||
|     private let dbWriter: any DatabaseWriter | ||||
|      | ||||
| 
 | ||||
|     /// The DatabaseMigrator that defines the database schema. | ||||
|     /// | ||||
|     /// See <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/migrations> | ||||
|     private var migrator: DatabaseMigrator { | ||||
|         var migrator = DatabaseMigrator() | ||||
|          | ||||
| 
 | ||||
|         #if DEBUG | ||||
|         // Speed up development by nuking the database when migrations change | ||||
|         // See <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/migrations> | ||||
|         migrator.eraseDatabaseOnSchemaChange = true | ||||
|         #endif | ||||
|          | ||||
| 
 | ||||
|         migrator.registerMigration("createLine") { db in | ||||
|             // Create a table | ||||
|             // See <https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/databaseschema> | ||||
|  | @ -39,6 +39,7 @@ struct AppDatabase { | |||
|                 t.autoIncrementedPrimaryKey("id") | ||||
|                 t.column("body", .text).notNull() | ||||
|                 t.column("chap", .integer).notNull() | ||||
|                 t.column("line_id", .integer).notNull() | ||||
|                 t.column("book", .text).notNull() | ||||
|                 t.column("verse", .integer) | ||||
|             } | ||||
|  | @ -70,18 +71,19 @@ struct AppDatabase { | |||
|                 t.column("scrollOffset", .integer).notNull() | ||||
|             } | ||||
| 
 | ||||
|             try db.create(table: "foo2") { t in | ||||
|             // change this to nuke/remake the database | ||||
|             try db.create(table: "foo1") { t in | ||||
|                 t.autoIncrementedPrimaryKey("id") | ||||
|                 t.column("ribbonId", .integer).notNull() | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|          | ||||
| 
 | ||||
|         // Migrations for future application versions will be inserted here: | ||||
|         // migrator.registerMigration(...) { db in | ||||
|         //     ... | ||||
|         // } | ||||
|          | ||||
| 
 | ||||
|         return migrator | ||||
|     } | ||||
| } | ||||
|  | @ -134,9 +136,9 @@ extension AppDatabase { | |||
|                 if (newPos < oldPos) { | ||||
| 
 | ||||
|             try db.execute(sql: """ | ||||
|                            UPDATE Ribbon  | ||||
|                            SET pos =  | ||||
|                            CASE  | ||||
|                            UPDATE Ribbon | ||||
|                            SET pos = | ||||
|                            CASE | ||||
|                            WHEN pos = ? THEN ? | ||||
|                            ELSE | ||||
|                            pos + 1 | ||||
|  | @ -148,9 +150,9 @@ extension AppDatabase { | |||
|                 print("DIFFFFF") | ||||
| 
 | ||||
|             try db.execute(sql: """ | ||||
|                            UPDATE Ribbon  | ||||
|                            SET pos =  | ||||
|                            CASE  | ||||
|                            UPDATE Ribbon | ||||
|                            SET pos = | ||||
|                            CASE | ||||
|                            WHEN pos = ? THEN ? | ||||
|                            ELSE | ||||
|                            pos - 1 | ||||
|  | @ -161,7 +163,7 @@ extension AppDatabase { | |||
|             } | ||||
| 
 | ||||
|             // try db.execute(sql: """ | ||||
|             //                UPDATE Ribbon  | ||||
|             //                UPDATE Ribbon | ||||
|             //                SET pos = ? | ||||
|             //                WHERE (id = ?) | ||||
|             //                """, arguments: [newPos, ribbon.id!]) | ||||
|  | @ -212,27 +214,36 @@ extension AppDatabase { | |||
|     } | ||||
| 
 | ||||
|     func importJson(_ filename: String, _ db: Database) throws { | ||||
|         let importJson : JsonImport = load(filename) | ||||
|         let importJson: JsonImport = load(filename) | ||||
| 
 | ||||
|         if try Line.all().isEmpty(db) { | ||||
|             for l in importJson.lines { | ||||
|                 print("importing Lines") | ||||
|                 _ = try l.inserted(db) | ||||
|         var x = 0 | ||||
|         // if try Line.all().isEmpty(db) { | ||||
|         for l in importJson.lines { | ||||
|             // print("importing Lines") | ||||
|             if x < 5 { | ||||
|                 print(l) | ||||
|                 x += 1 | ||||
|             } | ||||
|             _ = try l.inserted(db) | ||||
|         } | ||||
| 
 | ||||
|             for l in importJson.segs { | ||||
|                 print("importing SEGS") | ||||
|                 _ = try l.inserted(db) | ||||
|         x = 0 | ||||
|         for l in importJson.segs { | ||||
|             // print("importing SEGS") | ||||
| 
 | ||||
|             if x < 5 { | ||||
|                 print(l) | ||||
|                 x += 1 | ||||
|             } | ||||
|             _ = try l.inserted(db) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Create random Lines if the database is empty. | ||||
|     func initDatabase() throws { | ||||
|         do { | ||||
|         try dbWriter.write { db in | ||||
| 
 | ||||
|             if try Line.all().isEmpty(db) { | ||||
| 
 | ||||
|                 try importJson("john_export.json", db) | ||||
|                 try importJson("mark_export.json", db) | ||||
|                 _ = try Ribbon(id: 1, pos: 1, title: "John", book: "bible.john", scrollId: "1", scrollOffset: 0).inserted(db) | ||||
|  | @ -241,6 +252,11 @@ extension AppDatabase { | |||
|                 _ = try SelectedRibbon(id: 1, ribbonId: 1).inserted(db) | ||||
|             } | ||||
|         } | ||||
|         } catch { | ||||
|             print("Error info: \(error)") | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -214,14 +214,13 @@ struct ContentView: View { | |||
|     @State var readOffset = CGPoint() | ||||
|     @State var dragOffset = CGFloat() | ||||
| 
 | ||||
|     @Query(SegDenormRequest(book: "bible.john")) private var segs: [SegDenorm] | ||||
| 
 | ||||
|     @State var draggedRibbon: Ribbon? | ||||
|     @State var isDragging = false | ||||
| 
 | ||||
|     @Environment(\.appDatabase) private var appDatabase | ||||
| 
 | ||||
|     @Query(SegDenormRequest(book: "bible.mark")) private var segs: [SegDenorm] | ||||
| 
 | ||||
|     @Query(RibbonRequest()) private var ribbons: [Ribbon] | ||||
|     @Query<SelectedRibbonRequest> var selectedRibbon: [Ribbon] | ||||
| 
 | ||||
|  | @ -273,7 +272,6 @@ struct ContentView: View { | |||
|                 VStack { | ||||
|                     // Top pane | ||||
|                     Pane(paneConnector: paneConnector, | ||||
|                          segs: segs, | ||||
|                          selectedRibbon: selectedRibbon, | ||||
|                          width: geometry.size.width - 50, | ||||
|                          height: geometry.size.height / 2) | ||||
|  |  | |||
|  | @ -1,39 +1,17 @@ | |||
| import GRDB | ||||
| /// The Line struct. | ||||
| /// | ||||
| /// Identifiable conformance supports SwiftUI list animations, and type-safe | ||||
| /// GRDB primary key methods. | ||||
| /// Equatable conformance supports tests. | ||||
| struct Line: Identifiable, Equatable { | ||||
|     /// The player id. | ||||
|     /// | ||||
|     /// Int64 is the recommended type for auto-incremented database ids. | ||||
|     /// Use nil for players that are not inserted yet in the database. | ||||
|     var id: Int64? | ||||
|     var chap: Int | ||||
|     var line_id: Int // this is a line_id per book | ||||
|     var verse: Int | ||||
|     var body: String | ||||
|     var book: String | ||||
| } | ||||
| 
 | ||||
| extension Line { | ||||
| 
 | ||||
|     private static let books = [ | ||||
|         "John", "Matthew", "Imitation of Christ"] | ||||
|      | ||||
|     /// Creates a new player with empty name and zero score | ||||
|     // static func new() -> Line { | ||||
|     //     Line(id: nil, chap: 1, body: "") | ||||
|     // } | ||||
|      | ||||
|     /// Returns a random score | ||||
|     static func randomScore() -> Int { | ||||
|         10 * Int.random(in: 0...100) | ||||
|     } | ||||
| 
 | ||||
|     static func randomBook() -> String { | ||||
|         books.randomElement()! | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // MARK: - Persistence | ||||
|  | @ -47,7 +25,7 @@ extension Line: Codable, FetchableRecord, MutablePersistableRecord { | |||
|         static let id = Column(CodingKeys.id) | ||||
|         static let chap = Column(CodingKeys.chap) | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     /// Updates a player id after it has been inserted in the database. | ||||
|     mutating func didInsert(_ inserted: InsertionSuccess) { | ||||
|         id = inserted.rowID | ||||
|  | @ -73,7 +51,7 @@ extension DerivableRequest<Line> { | |||
|     //    // See https://github.com/groue/GRDB.swift/blob/master/README.md#string-comparison | ||||
|     //    order(Line.Columns.name.collating(.localizedCaseInsensitiveCompare)) | ||||
|     //} | ||||
|      | ||||
| 
 | ||||
|     ///// A request of players ordered by score. | ||||
|     ///// | ||||
|     ///// For example: | ||||
|  |  | |||
|  | @ -26,11 +26,11 @@ struct LineRequest: Queryable { | |||
|     var ordering: Ordering | ||||
|     var book: String | ||||
| 
 | ||||
|      | ||||
| 
 | ||||
|     // MARK: - Queryable Implementation | ||||
|      | ||||
| 
 | ||||
|     static var defaultValue: [Line] { [] } | ||||
|      | ||||
| 
 | ||||
|     func publisher(in appDatabase: AppDatabase) -> AnyPublisher<[Line], Error> { | ||||
|         // Build the publisher from the general-purpose read-only access | ||||
|         // granted by `appDatabase.reader`. | ||||
|  | @ -45,15 +45,16 @@ struct LineRequest: Queryable { | |||
|                 scheduling: .immediate) | ||||
|             .eraseToAnyPublisher() | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     // This method is not required by Queryable, but it makes it easier | ||||
|     // to test LineRequest. | ||||
|     func fetchValue(_ db: Database) throws -> [Line] { | ||||
|         if book == "" { | ||||
|             return try Line.filter(bookColumn ==  Line.randomBook()).fetchAll(db) | ||||
|         } else { | ||||
|             return try Line.filter(bookColumn ==  book).fetchAll(db) | ||||
|         } | ||||
|         return try Line.filter(bookColumn ==  book).fetchAll(db) | ||||
|         // if book == "" { | ||||
|         //     return try Line.filter(bookColumn ==  Line.randomBook()).fetchAll(db) | ||||
|         // } else { | ||||
|         //     return try Line.filter(bookColumn ==  book).fetchAll(db) | ||||
|         // } | ||||
|         // switch ordering { | ||||
|         // case .byScore: | ||||
|         //     return try Line.all().fetchAll(db) | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ import Foundation | |||
| extension AppDatabase { | ||||
|     /// The database for the application | ||||
|     static let shared = makeShared() | ||||
|      | ||||
| 
 | ||||
|     private static func makeShared() -> AppDatabase { | ||||
|         do { | ||||
|             // Pick a folder for storing the SQLite database, as well as | ||||
|  | @ -20,18 +20,18 @@ extension AppDatabase { | |||
|             if CommandLine.arguments.contains("-reset") { | ||||
|                 try? fileManager.removeItem(at: folderURL) | ||||
|             } | ||||
|              | ||||
| 
 | ||||
|             // Create the database folder if needed | ||||
|             try fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true) | ||||
|              | ||||
| 
 | ||||
|             // Connect to a database on disk | ||||
|             // See https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/databaseconnections | ||||
|             let dbURL = folderURL.appendingPathComponent("db.sqlite") | ||||
|             let dbPool = try DatabasePool(path: dbURL.path) | ||||
|              | ||||
| 
 | ||||
|             // Create the AppDatabase | ||||
|             let appDatabase = try AppDatabase(dbPool) | ||||
|              | ||||
| 
 | ||||
|             // // Prepare the database with test fixtures if requested | ||||
|             // if CommandLine.arguments.contains("-fixedTestData") { | ||||
|             //     try appDatabase.createPlayersForUITests() | ||||
|  | @ -40,9 +40,10 @@ extension AppDatabase { | |||
|             //     // demo purpose. | ||||
|             //     try appDatabase.createRandomPlayersIfEmpty() | ||||
|             // } | ||||
|             print("initing database") | ||||
|             try appDatabase.initDatabase() | ||||
| 
 | ||||
|              | ||||
| 
 | ||||
|             return appDatabase | ||||
|         } catch { | ||||
|             // Replace this implementation with code to handle the error appropriately. | ||||
|  | @ -57,7 +58,7 @@ extension AppDatabase { | |||
|             fatalError("Unresolved error \(error)") | ||||
|         } | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     /// Creates an empty database for SwiftUI previews | ||||
|     static func empty() -> AppDatabase { | ||||
|         // Connect to an in-memory database | ||||
|  | @ -65,7 +66,7 @@ extension AppDatabase { | |||
|         let dbQueue = try! DatabaseQueue() | ||||
|         return try! AppDatabase(dbQueue) | ||||
|     } | ||||
|      | ||||
| 
 | ||||
|     /// Creates a database full of random players for SwiftUI previews | ||||
|    static func random() -> AppDatabase { | ||||
|        let appDatabase = empty() | ||||
|  |  | |||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
		Loading…
	
		Reference in New Issue