Add export and import
This commit is contained in:
parent
911fc0c8f8
commit
cd37df22bf
@ -37,6 +37,7 @@
|
||||
E267D1BA2A8F9D9C0069112B /* SkillStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E267D1B92A8F9D9C0069112B /* SkillStyle.swift */; };
|
||||
E267D1BC2A8FFF300069112B /* TagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E267D1BB2A8FFF300069112B /* TagView.swift */; };
|
||||
E267D1C02A9009780069112B /* CVLanguage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E267D1BF2A9009780069112B /* CVLanguage.swift */; };
|
||||
E267D1C22A9287900069112B /* Titled.swift in Sources */ = {isa = PBXBuildFile; fileRef = E267D1C12A9287900069112B /* Titled.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
@ -71,6 +72,7 @@
|
||||
E267D1B92A8F9D9C0069112B /* SkillStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SkillStyle.swift; sourceTree = "<group>"; };
|
||||
E267D1BB2A8FFF300069112B /* TagView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagView.swift; sourceTree = "<group>"; };
|
||||
E267D1BF2A9009780069112B /* CVLanguage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CVLanguage.swift; sourceTree = "<group>"; };
|
||||
E267D1C12A9287900069112B /* Titled.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Titled.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@ -138,6 +140,7 @@
|
||||
E267D1A12A8E45AE0069112B /* SkillsSet.swift */,
|
||||
E267D19D2A8E45540069112B /* TopInfo.swift */,
|
||||
E267D1AE2A8F69830069112B /* Publication.swift */,
|
||||
E267D1C12A9287900069112B /* Titled.swift */,
|
||||
);
|
||||
path = Data;
|
||||
sourceTree = "<group>";
|
||||
@ -283,6 +286,7 @@
|
||||
E267D1A72A8ECC170069112B /* ContentView.swift in Sources */,
|
||||
E267D18E2A8E1BEE0069112B /* RightImageLabel.swift in Sources */,
|
||||
E267D1962A8E3E760069112B /* TitledTextSection.swift in Sources */,
|
||||
E267D1C22A9287900069112B /* Titled.swift in Sources */,
|
||||
E267D1B82A8F9A2A0069112B /* HeaderStyle.swift in Sources */,
|
||||
E267D18A2A8E140E0069112B /* LeftImageLabel.swift in Sources */,
|
||||
E267D19A2A8E44F40069112B /* TitledIconSection.swift in Sources */,
|
||||
|
@ -1,17 +1,19 @@
|
||||
import SwiftUI
|
||||
import SFSafeSymbols
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
struct ContentView: View {
|
||||
|
||||
@Environment(\.colorScheme)
|
||||
var defaultColorScheme: ColorScheme
|
||||
|
||||
let content: [CVInfo]
|
||||
@State
|
||||
var content: [CVInfo]
|
||||
|
||||
let style: CVStyle
|
||||
|
||||
init(content: [CVInfo], style: CVStyle) {
|
||||
self.content = content
|
||||
self._content = .init(initialValue: content)
|
||||
self.style = style
|
||||
}
|
||||
|
||||
@ -40,16 +42,24 @@ struct ContentView: View {
|
||||
Text(content.element.language)
|
||||
.tag(content.offset)
|
||||
}
|
||||
}.frame(width: 100)
|
||||
}.frame(maxWidth: 100)
|
||||
Button(action: createAndSavePDF) {
|
||||
Label("Export PDF", systemSymbol: .squareAndArrowDown)
|
||||
Label("Save PDF", systemSymbol: .squareAndArrowDown)
|
||||
.padding(3)
|
||||
}
|
||||
Button(action: exportData) {
|
||||
Label("Export", systemSymbol: .squareAndArrowUp)
|
||||
.padding(3)
|
||||
}
|
||||
Button(action: importData) {
|
||||
Label("Import", systemSymbol: .docBadgePlus)
|
||||
.padding(8)
|
||||
}
|
||||
.padding()
|
||||
Spacer()
|
||||
Toggle("Dark mode", isOn: $darkModeEnabled)
|
||||
.toggleStyle(SwitchToggleStyle())
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.padding()
|
||||
ScrollView(.vertical) {
|
||||
CV(info: info, style: style)
|
||||
}.frame(width: style.pageWidth)
|
||||
@ -64,28 +74,111 @@ struct ContentView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func exportData() {
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = .prettyPrinted
|
||||
let data: Data
|
||||
do {
|
||||
data = try encoder.encode(info)
|
||||
} catch {
|
||||
print("Failed to encode data: \(error)")
|
||||
return
|
||||
}
|
||||
guard let url = showDataSavePanel() else {
|
||||
print("No url to save data")
|
||||
return
|
||||
}
|
||||
do {
|
||||
try data.write(to: url)
|
||||
} catch {
|
||||
print("Failed to write data: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private func importData() {
|
||||
guard let url = showOpenFilePanel() else {
|
||||
print("No url to import")
|
||||
return
|
||||
}
|
||||
let data: Data
|
||||
do {
|
||||
data = try Data(contentsOf: url)
|
||||
} catch {
|
||||
print("Failed to open file: \(error)")
|
||||
return
|
||||
}
|
||||
let newData: CVInfo
|
||||
do {
|
||||
newData = try JSONDecoder().decode(CVInfo.self, from: data)
|
||||
} catch {
|
||||
print("Failed to decode data: \(error)")
|
||||
return
|
||||
}
|
||||
guard let index = content.firstIndex(where: { $0.language == newData.language }) else {
|
||||
content.append(newData)
|
||||
selectedLanguageIndex = content.count - 1
|
||||
return
|
||||
}
|
||||
content[index] = newData
|
||||
selectedLanguageIndex = index
|
||||
}
|
||||
|
||||
private func createAndSavePDF() {
|
||||
DispatchQueue.main.async {
|
||||
guard let pdfURL = renderPDF() else {
|
||||
return
|
||||
}
|
||||
guard let url = showSavePanel() else {
|
||||
guard let url = showPdfSavePanel() else {
|
||||
print("No url to save PDF")
|
||||
return
|
||||
}
|
||||
writePDF(at: pdfURL, to: url)
|
||||
}
|
||||
}
|
||||
|
||||
private func showSavePanel() -> URL? {
|
||||
private func showOpenFilePanel() -> URL? {
|
||||
let panel = NSOpenPanel()
|
||||
panel.allowedContentTypes = [.json]
|
||||
panel.canCreateDirectories = true
|
||||
panel.isExtensionHidden = false
|
||||
panel.allowsOtherFileTypes = false
|
||||
panel.title = "Load JSON"
|
||||
panel.message = "Choose a JSON file of resume data to import"
|
||||
panel.nameFieldLabel = "File name:"
|
||||
panel.nameFieldStringValue = "CV.json"
|
||||
let response = panel.runModal()
|
||||
guard response == .OK else {
|
||||
return nil
|
||||
}
|
||||
return panel.url
|
||||
}
|
||||
|
||||
private func showDataSavePanel() -> URL? {
|
||||
showPanel(
|
||||
type: .json,
|
||||
title: "Save JSON",
|
||||
fileName: "CV.json",
|
||||
message: "Choose a location to save a JSON file of the resume data")
|
||||
}
|
||||
|
||||
private func showPdfSavePanel() -> URL? {
|
||||
showPanel(
|
||||
type: .pdf,
|
||||
title: "Save PDF",
|
||||
fileName: "CV.pdf",
|
||||
message: "Choose a location to save a PDF of the resume")
|
||||
}
|
||||
|
||||
private func showPanel(type: UTType, title: String, fileName: String, message: String) -> URL? {
|
||||
let savePanel = NSSavePanel()
|
||||
savePanel.allowedContentTypes = [.pdf]
|
||||
savePanel.allowedContentTypes = [type]
|
||||
savePanel.canCreateDirectories = true
|
||||
savePanel.isExtensionHidden = false
|
||||
savePanel.allowsOtherFileTypes = false
|
||||
savePanel.title = "Save PDF"
|
||||
savePanel.message = "Choose a location to save a PDF of the resume"
|
||||
savePanel.title = title
|
||||
savePanel.message = message
|
||||
savePanel.nameFieldLabel = "File name:"
|
||||
savePanel.nameFieldStringValue = "CV.pdf"
|
||||
savePanel.nameFieldStringValue = fileName
|
||||
|
||||
let response = savePanel.runModal()
|
||||
guard response == .OK else {
|
||||
|
@ -1,12 +1,5 @@
|
||||
import Foundation
|
||||
|
||||
struct Titled<Content> {
|
||||
|
||||
let title: String
|
||||
|
||||
let items: [Content]
|
||||
}
|
||||
|
||||
struct CVInfo {
|
||||
|
||||
let language: String
|
||||
@ -47,3 +40,7 @@ extension CVInfo: Hashable {
|
||||
hasher.combine(language)
|
||||
}
|
||||
}
|
||||
|
||||
extension CVInfo: Codable {
|
||||
|
||||
}
|
||||
|
@ -16,3 +16,7 @@ struct CareerStation: Identifiable {
|
||||
title + time + location
|
||||
}
|
||||
}
|
||||
|
||||
extension CareerStation: Codable {
|
||||
|
||||
}
|
||||
|
@ -10,3 +10,7 @@ struct Publication: Identifiable {
|
||||
title + venue
|
||||
}
|
||||
}
|
||||
|
||||
extension Publication: Codable {
|
||||
|
||||
}
|
||||
|
@ -11,3 +11,26 @@ struct SkillsSet: Identifiable {
|
||||
entries.joined()
|
||||
}
|
||||
}
|
||||
|
||||
extension SkillsSet: Decodable {
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case systemSymbol
|
||||
case entries
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
let rawSymbol = try container.decode(String.self, forKey: .systemSymbol)
|
||||
self.systemSymbol = .init(rawValue: rawSymbol)
|
||||
self.entries = try container.decode([String].self, forKey: .entries)
|
||||
}
|
||||
}
|
||||
|
||||
extension SkillsSet: Encodable {
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(systemSymbol.rawValue, forKey: .systemSymbol)
|
||||
try container.encode(entries, forKey: .entries)
|
||||
}
|
||||
}
|
||||
|
12
ResumeBuilder/Data/Titled.swift
Normal file
12
ResumeBuilder/Data/Titled.swift
Normal file
@ -0,0 +1,12 @@
|
||||
import Foundation
|
||||
|
||||
struct Titled<Content> {
|
||||
|
||||
let title: String
|
||||
|
||||
let items: [Content]
|
||||
}
|
||||
|
||||
extension Titled: Codable where Content: Codable {
|
||||
|
||||
}
|
@ -20,3 +20,7 @@ struct TopInfo {
|
||||
|
||||
let github: String
|
||||
}
|
||||
|
||||
extension TopInfo: Codable {
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user